Repository: osbuild/osbuild-composer
Pull request #2298 (2022-02-14 11:33:04)

cloudapi/v2: add error object to ImageStatus
Add an error object to the ComposeStatus.ImageStatus. The error object contains a human-readable error reason
and optional details in the case of an error.


This pull request includes:

- [ ] adequate testing for the new functionality or fixed issue
- [ ] adequate documentation informing people about the change such as
  - [ ] submit a PR for the [guides](https://github.com/osbuild/guides) repository if this PR changed any behavior described there: https://www.osbuild.org/guides/

<!--
Thanks for proposing a change to osbuild-composer!

Please don't remove the above check list. These are things that each pull
request must have before it is merged. It helps maintainers to not forget
anything.

If the reason for ticking any of the boxes is ambiguous, please add a short
note explaining why.

In addition, if this pull request fixes a downstream issue, please refer to
test/README.md and add these additional items:

- [ ] 1st commit of any `rhbz#` related PR contains bug reproducer; CI reports FAIL or
- [ ] PR contains automated tests for new functionality and
- [ ] QE has approved reproducer/new tests and
- [ ] Subsequent commits provide bug fixes without modifying the reproducer; CI reports PASS and
- [ ] QE approves this PR; RHBZ status is set to `MODIFIED + Verified=Tested`
-->


kingsleyzissou: @teg that hadn't crossed my mind. I can make those changes. Do you thing returning the error code would be enough or is there other data you suggest returning?
teg: > @teg that hadn't crossed my mind. I can make those changes. Do you thing returning the error code would be enough or is there other data you suggest returning?

I would say a good starting point would be enough structured data to recreate the human readable strings, then we can extend it over time?
Gundersanne: I actually kinda like the idea of the human readable string that we can just propagate all the way through :) But why not both? Have the reason and details in there, but just also include the code? The code is meant to be stable after all.
kingsleyzissou: @teg after thinking about Sanne's comment for a bit. If we were to handle the error in the frontend and create the human readable string would we not have to copy across some of the logic from the worker to translate the error codes?

For now, I've added the code to the error object but haven't removed the reason just yet. I guess we could still expand the object to add additional info similar to what we do here for example:
https://github.com/osbuild/osbuild-composer/blob/main/internal/cloudapi/v2/errors.go#L167-L176
But those other fields are probably more than we need any way.
Gundersanne: @teg further thoughts on this? I actually though the single string (reason) returned in the ComposeStatusError would be easy to present in the UI. It's a lot easier to present a single string than to present structured data depending on the error code. We can still do the latter using the code + details for specific codes, but we can always show the single string.

That said we should probably improve the strings, "osbuild build failed" isn't a very good error string :| But out of context for this PR.
teg: > @teg further thoughts on this?

Sorry for dropping the ball on this.

I think it is fine as it is, though I think in the future we should discuss amending it with structured data so that consumers could choose to ignore the string.

That requires more discussion though, and no need to block on that.

Patch:
diff --git a/cmd/osbuild-worker/jobimpl-osbuild.go b/cmd/osbuild-worker/jobimpl-osbuild.go
index 121db0d14..5a95ce281 100644
--- a/cmd/osbuild-worker/jobimpl-osbuild.go
+++ b/cmd/osbuild-worker/jobimpl-osbuild.go
@@ -36,6 +36,7 @@ type OSBuildJobImpl struct {
 	GCPCreds    []byte
 	AzureCreds  *azure.Credentials
 	AWSCreds    string
+	AWSBucket   string
 }
 
 // Returns an *awscloud.AWS object with the credentials of the request. If they
@@ -268,13 +269,17 @@ func (impl *OSBuildJobImpl) Run(job worker.Job) error {
 				key = uuid.New().String()
 			}
 
-			_, err = a.Upload(path.Join(outputDirectory, exportPath, options.Filename), options.Bucket, key)
+			bucket := options.Bucket
+			if impl.AWSBucket != "" {
+				bucket = impl.AWSBucket
+			}
+			_, err = a.Upload(path.Join(outputDirectory, exportPath, options.Filename), bucket, key)
 			if err != nil {
 				osbuildJobResult.JobError = clienterrors.WorkerClientError(clienterrors.ErrorUploadingImage, err.Error())
 				return nil
 			}
 
-			ami, err := a.Register(args.Targets[0].ImageName, options.Bucket, key, options.ShareWithAccounts, common.CurrentArch())
+			ami, err := a.Register(args.Targets[0].ImageName, bucket, key, options.ShareWithAccounts, common.CurrentArch())
 			if err != nil {
 				osbuildJobResult.JobError = clienterrors.WorkerClientError(clienterrors.ErrorImportingImage, err.Error())
 				return nil
@@ -305,12 +310,16 @@ func (impl *OSBuildJobImpl) Run(job worker.Job) error {
 			}
 			key += "-" + options.Filename
 
-			_, err = a.Upload(path.Join(outputDirectory, exportPath, options.Filename), options.Bucket, key)
+			bucket := options.Bucket
+			if impl.AWSBucket != "" {
+				bucket = impl.AWSBucket
+			}
+			_, err = a.Upload(path.Join(outputDirectory, exportPath, options.Filename), bucket, key)
 			if err != nil {
 				osbuildJobResult.JobError = clienterrors.WorkerClientError(clienterrors.ErrorUploadingImage, err.Error())
 				return nil
 			}
-			url, err := a.S3ObjectPresignedURL(options.Bucket, key)
+			url, err := a.S3ObjectPresignedURL(bucket, key)
 			if err != nil {
 				osbuildJobResult.JobError = clienterrors.WorkerClientError(clienterrors.ErrorUploadingImage, err.Error())
 				return nil
diff --git a/cmd/osbuild-worker/main.go b/cmd/osbuild-worker/main.go
index 5105c3fe3..5e681689c 100644
--- a/cmd/osbuild-worker/main.go
+++ b/cmd/osbuild-worker/main.go
@@ -137,6 +137,7 @@ func main() {
 		} `toml:"azure"`
 		AWS *struct {
 			Credentials string `toml:"credentials"`
+			Bucket      string `toml:"bucket"`
 		} `toml:"aws"`
 		Authentication *struct {
 			OAuthURL         string `toml:"oauth_url"`
diff --git a/internal/cloudapi/v2/openapi.v2.gen.go b/internal/cloudapi/v2/openapi.v2.gen.go
index a6e5e7e67..124e7df1e 100644
--- a/internal/cloudapi/v2/openapi.v2.gen.go
+++ b/internal/cloudapi/v2/openapi.v2.gen.go
@@ -206,6 +206,13 @@ type ComposeStatus struct {
 	Status        ComposeStatusValue `json:"status"`
 }
 
+// ComposeStatusError defines model for ComposeStatusError.
+type ComposeStatusError struct {
+	Details *interface{} `json:"details,omitempty"`
+	Id      int          `json:"id"`
+	Reason  string       `json:"reason"`
+}
+
 // ComposeStatusValue defines model for ComposeStatusValue.
 type ComposeStatusValue string
 
@@ -297,8 +304,9 @@ type ImageRequest struct {
 
 // ImageStatus defines model for ImageStatus.
 type ImageStatus struct {
-	Status       ImageStatusValue `json:"status"`
-	UploadStatus *UploadStatus    `json:"upload_status,omitempty"`
+	Error        *ComposeStatusError `json:"error,omitempty"`
+	Status       ImageStatusValue    `json:"status"`
+	UploadStatus *UploadStatus       `json:"upload_status,omitempty"`
 }
 
 // ImageStatusValue defines model for ImageStatusValue.
@@ -642,79 +650,80 @@ func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL
 // Base64 encoded, gzipped, json marshaled Swagger object
 var swaggerSpec = []string{
 
-	"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",
+	"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",
+	"D7LRSqTlE6SKzTY6Nw//FwAA///Ls+uMj1AAAA==",
 }
 
 // GetSwagger returns the content of the embedded swagger specification file
diff --git a/internal/cloudapi/v2/openapi.v2.yml b/internal/cloudapi/v2/openapi.v2.yml
index 84ec6e3a1..3efd3705f 100644
--- a/internal/cloudapi/v2/openapi.v2.yml
+++ b/internal/cloudapi/v2/openapi.v2.yml
@@ -472,6 +472,18 @@ components:
           $ref: '#/components/schemas/ImageStatusValue'
         upload_status:
           $ref: '#/components/schemas/UploadStatus'
+        error:
+          $ref: '#/components/schemas/ComposeStatusError'
+    ComposeStatusError:
+      required:
+       - id
+       - reason
+      properties:
+        id:
+          type: integer
+        reason:
+          type: string
+        details: {}
     ImageStatusValue:
       type: string
       enum: ['success', 'failure', 'pending', 'building', 'uploading', 'registering']
diff --git a/internal/cloudapi/v2/v2.go b/internal/cloudapi/v2/v2.go
index 7835de013..6d6d1b51b 100644
--- a/internal/cloudapi/v2/v2.go
+++ b/internal/cloudapi/v2/v2.go
@@ -823,6 +823,7 @@ func (h *apiHandlers) GetComposeStatus(ctx echo.Context, id string) error {
 			Status: composeStatusFromOSBuildJobStatus(status, &result),
 			ImageStatus: ImageStatus{
 				Status:       imageStatusFromOSBuildJobStatus(status, &result),
+				Error:        composeStatusErrorFromJobError(result.JobError),
 				UploadStatus: us,
 			},
 		})
@@ -851,6 +852,7 @@ func (h *apiHandlers) GetComposeStatus(ctx echo.Context, id string) error {
 			buildJobResults = append(buildJobResults, buildJobResult)
 			buildJobStatuses = append(buildJobStatuses, ImageStatus{
 				Status: imageStatusFromKojiJobStatus(buildJobStatus, &initResult, &buildJobResult),
+				Error:  composeStatusErrorFromJobError(result.JobError),
 			})
 		}
 		response := ComposeStatus{
@@ -874,6 +876,17 @@ func (h *apiHandlers) GetComposeStatus(ctx echo.Context, id string) error {
 	}
 }
 
+func composeStatusErrorFromJobError(jobError *clienterrors.Error) *ComposeStatusError {
+	if jobError == nil {
+		return nil
+	}
+	return &ComposeStatusError{
+		Id:      int(jobError.ID),
+		Reason:  jobError.Reason,
+		Details: &jobError.Details,
+	}
+}
+
 func imageStatusFromOSBuildJobStatus(js *worker.JobStatus, result *worker.OSBuildJobResult) ImageStatusValue {
 	if js.Canceled {
 		return ImageStatusValueFailure
diff --git a/internal/worker/server.go b/internal/worker/server.go
index b42fdb3d9..a85723264 100644
--- a/internal/worker/server.go
+++ b/internal/worker/server.go
@@ -147,7 +147,7 @@ func (s *Server) OSBuildJobStatus(id uuid.UUID, result *OSBuildJobResult) (*JobS
 		return nil, nil, fmt.Errorf("expected osbuild:*, found %q job instead", jobType)
 	}
 
-	if result.JobError == nil {
+	if result.JobError == nil && !status.Finished.IsZero() {
 		if result.OSBuildOutput == nil {
 			result.JobError = clienterrors.WorkerClientError(clienterrors.ErrorBuildJob, "osbuild build failed")
 		} else if len(result.OSBuildOutput.Error) > 0 {
@@ -175,7 +175,7 @@ func (s *Server) OSBuildKojiJobStatus(id uuid.UUID, result *OSBuildKojiJobResult
 		return nil, nil, fmt.Errorf("expected \"osbuild-koji:*\", found %q job instead", jobType)
 	}
 
-	if result.JobError == nil {
+	if result.JobError == nil && !status.Finished.IsZero() {
 		if result.OSBuildOutput == nil {
 			result.JobError = clienterrors.WorkerClientError(clienterrors.ErrorBuildJob, "osbuild build failed")
 		} else if len(result.OSBuildOutput.Error) > 0 {
diff --git a/templates/packer/ansible/roles/common/files/worker-initialization-scripts/worker_service.sh b/templates/packer/ansible/roles/common/files/worker-initialization-scripts/worker_service.sh
index 0eb0b7f48..cc848d3a8 100755
--- a/templates/packer/ansible/roles/common/files/worker-initialization-scripts/worker_service.sh
+++ b/templates/packer/ansible/roles/common/files/worker-initialization-scripts/worker_service.sh
@@ -14,7 +14,8 @@ credentials = "/etc/osbuild-worker/gcp_credentials.json"
 [azure]
 credentials = "/etc/osbuild-worker/azure_credentials.toml"
 [aws]
-credentials = "/etc/osbuild-worker/aws_credentials.toml"
+credentials = "${WORKER_CONFIG_AWS_CREDENTIALS:-}"
+bucket = "${WORKER_CONFIG_AWS_BUCKET:-}"
 EOF
 
 # Prepare osbuild-composer's remote worker services and sockets.


Test patch:
diff --git a/internal/cloudapi/v2/v2_test.go b/internal/cloudapi/v2/v2_test.go
index 7e28f48be..97fd24b87 100644
--- a/internal/cloudapi/v2/v2_test.go
+++ b/internal/cloudapi/v2/v2_test.go
@@ -16,6 +16,7 @@ import (
 	"github.com/osbuild/osbuild-composer/internal/jobqueue"
 	"github.com/osbuild/osbuild-composer/internal/jobqueue/fsjobqueue"
 	distro_mock "github.com/osbuild/osbuild-composer/internal/mocks/distro"
+	"github.com/osbuild/osbuild-composer/internal/osbuild2"
 	"github.com/osbuild/osbuild-composer/internal/ostree/mock_ostree_repo"
 	"github.com/osbuild/osbuild-composer/internal/rpmmd"
 	"github.com/osbuild/osbuild-composer/internal/test"
@@ -600,7 +601,8 @@ func TestComposeStatusSuccess(t *testing.T) {
 	}`, jobId, jobId))
 
 	res, err := json.Marshal(&worker.OSBuildJobResult{
-		Success: true,
+		Success:       true,
+		OSBuildOutput: &osbuild2.Result{},
 	})
 	require.NoError(t, err)
 
@@ -672,7 +674,14 @@ func TestComposeStatusFailure(t *testing.T) {
 		"href": "/api/image-builder-composer/v2/composes/%v",
 		"kind": "ComposeStatus",
 		"id": "%v",
-		"image_status": {"status": "failure"},
+		"image_status": {
+			"error": {
+				"id": 10,
+				"details": null,
+				"reason": "osbuild build failed"
+			},
+			"status": "failure"
+		},
 		"status": "failure"
 	}`, jobId, jobId))
 }
@@ -727,7 +736,14 @@ func TestComposeLegacyError(t *testing.T) {
 		"href": "/api/image-builder-composer/v2/composes/%v",
 		"kind": "ComposeStatus",
 		"id": "%v",
-		"image_status": {"status": "failure"},
+		"image_status": {
+			"error": {
+				"id": 10,
+				"details": null,
+				"reason": "osbuild build failed"
+			},
+			"status": "failure"
+		},
 		"status": "failure"
 	}`, jobId, jobId))
 }
@@ -785,7 +801,14 @@ func TestComposeJobError(t *testing.T) {
 		"href": "/api/image-builder-composer/v2/composes/%v",
 		"kind": "ComposeStatus",
 		"id": "%v",
-		"image_status": {"status": "failure"},
+		"image_status": {
+			"error": {
+				"id": 10,
+				"details": null,
+				"reason": "Error building image"
+			},
+			"status": "failure"
+		},
 		"status": "failure"
 	}`, jobId, jobId))
 }