cloudapi: support container embedding

Add support for embedding container images via the cloud API. For
this the container resolve job was plumbed into the cloud api's
handler and the API specification updated with a new `containers`
section that mimics the blueprint section with the same name.
This commit is contained in:
Christian Kellner 2022-07-27 15:52:24 +02:00 committed by Tomáš Hozza
parent 45850639a0
commit 388154d7f6
7 changed files with 341 additions and 119 deletions

View file

@ -31,6 +31,7 @@ const (
ErrorTargetError ClientErrorCode = 28
ErrorParsingJobArgs ClientErrorCode = 29
ErrorContainerResolution ClientErrorCode = 30
ErrorContainerDependency ClientErrorCode = 31
)
type ClientErrorCode int
@ -94,6 +95,8 @@ func GetStatusCode(err *Error) StatusCode {
// IsDependencyError returns true if the error means that a dependency of a job failed
func (e *Error) IsDependencyError() bool {
switch e.ID {
case ErrorContainerDependency:
return true
case ErrorDepsolveDependency:
return true
case ErrorManifestDependency:

View file

@ -146,8 +146,11 @@ func (s *Server) EnqueueDepsolve(job *DepsolveJob, channel string) (uuid.UUID, e
return s.enqueue(JobTypeDepsolve, job, nil, channel)
}
func (s *Server) EnqueueManifestJobByID(job *ManifestJobByID, parent uuid.UUID, channel string) (uuid.UUID, error) {
return s.enqueue(JobTypeManifestIDOnly, job, []uuid.UUID{parent}, channel)
func (s *Server) EnqueueManifestJobByID(job *ManifestJobByID, dependencies []uuid.UUID, channel string) (uuid.UUID, error) {
if len(dependencies) == 0 {
panic("EnqueueManifestJobByID has no dependencies, expected at least a depsolve job")
}
return s.enqueue(JobTypeManifestIDOnly, job, dependencies, channel)
}
func (s *Server) EnqueueContainerResolveJob(job *ContainerResolveJob, channel string) (uuid.UUID, error) {
@ -622,6 +625,14 @@ func (s *Server) FinishJob(token uuid.UUID, result json.RawMessage) error {
}
jobResult = &kojiFinalizeJR.JobResult
case JobTypeContainerResolve:
var containerResolveJR ContainerResolveJobResult
jobInfo, err = s.ContainerResolveJobInfo(jobId, &containerResolveJR)
if err != nil {
return err
}
jobResult = &containerResolveJR.JobResult
default:
return fmt.Errorf("unexpected job type: %s", jobType)
}

View file

@ -372,7 +372,7 @@ func TestRequestJobById(t *testing.T) {
depsolveJobId, err := server.EnqueueDepsolve(&worker.DepsolveJob{}, "")
require.NoError(t, err)
jobId, err := server.EnqueueManifestJobByID(&worker.ManifestJobByID{}, depsolveJobId, "")
jobId, err := server.EnqueueManifestJobByID(&worker.ManifestJobByID{}, []uuid.UUID{depsolveJobId}, "")
require.NoError(t, err)
test.TestRoute(t, server.Handler(), false, "POST", "/api/worker/v1/jobs", fmt.Sprintf(`{"arch":"arch","types":["%s"]}`, worker.JobTypeManifestIDOnly), http.StatusBadRequest,
@ -742,10 +742,10 @@ func enqueueAndFinishTestJobDependencies(s *worker.Server, deps []testJob) ([]uu
case *worker.ManifestJobByID:
job := dep.main.(*worker.ManifestJobByID)
if len(depUUIDs) != 1 {
return nil, fmt.Errorf("exactly one dependency is expected for ManifestJobByID, got: %d", len(depUUIDs))
if len(depUUIDs) < 1 {
return nil, fmt.Errorf("at least one dependency is expected for ManifestJobByID, got: %d", len(depUUIDs))
}
id, err = s.EnqueueManifestJobByID(job, depUUIDs[0], "")
id, err = s.EnqueueManifestJobByID(job, depUUIDs, "")
if err != nil {
return nil, err
}
@ -780,6 +780,16 @@ func enqueueAndFinishTestJobDependencies(s *worker.Server, deps []testJob) ([]uu
return nil, err
}
case *worker.ContainerResolveJob:
job := dep.main.(*worker.ContainerResolveJob)
if len(depUUIDs) != 0 {
return nil, fmt.Errorf("dependencies are not supported for ContainerResolveJob, got: %d", len(depUUIDs))
}
id, err = s.EnqueueContainerResolveJob(job, "")
if err != nil {
return nil, err
}
default:
return nil, fmt.Errorf("unexpected job type")
}
@ -942,6 +952,71 @@ func TestJobDependencyChainErrors(t *testing.T) {
Reason: "empty manifest received",
},
},
// osbuild + manifest + depsolve + container resolve
// failed container resolve
{
job: testJob{
main: &worker.OSBuildJob{},
deps: []testJob{
{
main: &worker.KojiInitJob{},
result: &worker.KojiInitJobResult{},
},
{
main: &worker.ManifestJobByID{},
deps: []testJob{
{
main: &worker.ContainerResolveJob{},
result: &worker.ContainerResolveJobResult{
JobResult: worker.JobResult{
JobError: &clienterrors.Error{
ID: clienterrors.ErrorContainerResolution,
Reason: "remote container not found",
},
},
},
},
{
main: &worker.DepsolveJob{},
result: &worker.DepsolveJobResult{},
},
},
result: &worker.ManifestJobByIDResult{
JobResult: worker.JobResult{
JobError: &clienterrors.Error{
ID: clienterrors.ErrorContainerDependency,
Reason: "container dependency job failed",
},
},
},
},
},
result: &worker.OSBuildJobResult{
JobResult: worker.JobResult{
JobError: &clienterrors.Error{
ID: clienterrors.ErrorManifestDependency,
Reason: "manifest dependency job failed",
},
},
},
},
expectedError: &clienterrors.Error{
ID: clienterrors.ErrorManifestDependency,
Reason: "manifest dependency job failed",
Details: []*clienterrors.Error{
{
ID: clienterrors.ErrorContainerDependency,
Reason: "container dependency job failed",
Details: []*clienterrors.Error{
{
ID: clienterrors.ErrorContainerResolution,
Reason: "remote container not found",
},
},
},
},
},
},
// koji-init + osbuild + manifest + depsolve
// failed depsolve
{
@ -1325,7 +1400,7 @@ func TestJobDependencyChainErrors(t *testing.T) {
Reason: "koji-finalize failed",
},
},
// koji-init + (osbuild + manifest + depsolve) + (osbuild + manifest + depsolve) + koji-finalize
// koji-init + (osbuild + manifest + depsolve + container resolve) + (osbuild + manifest + depsolve) + koji-finalize
// all passed
{
job: testJob{
@ -1346,6 +1421,10 @@ func TestJobDependencyChainErrors(t *testing.T) {
{
main: &worker.ManifestJobByID{},
deps: []testJob{
{
main: &worker.ContainerResolveJob{},
result: &worker.ContainerResolveJobResult{},
},
{
main: &worker.DepsolveJob{},
result: &worker.DepsolveJobResult{},