debian-forge-composer/internal/osbuild1/result.go
Achilleas Koutsou 007d52f171 osbuild1/result: read v2 output and convert to v1
Log output from osbuild has a very different format when using the
new schema.  The osbuild1.Result object now supports unmarshalling the
new format and adapting it to the old format.

The most important field to set is the Success field to signal whether
the build succeeded.

Secondarily, it also copies over the output from each stage in order to
provide build job log output through the weldr API.
Since the new format contains multiple pipelines with multiple stages
each, the stages are flattened to fit the old format.  A unique name for
each stage is created by prepending the name of the pipeline to its
index in the pipeline and its type.
2021-03-17 18:12:17 +00:00

179 lines
4.7 KiB
Go

package osbuild1
import (
"bytes"
"encoding/json"
"fmt"
"io"
"github.com/osbuild/osbuild-composer/internal/osbuild2"
)
type rawAssemblerResult struct {
Name string `json:"name"`
Options json.RawMessage `json:"options"`
Success bool `json:"success"`
Output string `json:"output"`
}
type StageResult struct {
Name string `json:"name"`
Options json.RawMessage `json:"options"`
Success bool `json:"success"`
Output string `json:"output"`
Metadata StageMetadata `json:"metadata"`
}
// StageMetadata specify the metadata of a given stage-type.
type StageMetadata interface {
isStageMetadata()
}
type rawStageResult struct {
Name string `json:"name"`
Options json.RawMessage `json:"options"`
Success bool `json:"success"`
Output string `json:"output"`
Metadata json.RawMessage `json:"metadata"`
}
type buildResult struct {
Stages []StageResult `json:"stages"`
TreeID string `json:"tree_id"`
Success bool `json:"success"`
}
type Result struct {
TreeID string `json:"tree_id"`
OutputID string `json:"output_id"`
Build *buildResult `json:"build"`
Stages []StageResult `json:"stages"`
Assembler *rawAssemblerResult `json:"assembler"`
Success bool `json:"success"`
}
func (result *StageResult) UnmarshalJSON(data []byte) error {
var rawStageResult rawStageResult
err := json.Unmarshal(data, &rawStageResult)
if err != nil {
return err
}
var metadata StageMetadata
switch rawStageResult.Name {
case "org.osbuild.rpm":
metadata = new(RPMStageMetadata)
err = json.Unmarshal(rawStageResult.Metadata, metadata)
if err != nil {
return err
}
default:
metadata = nil
}
result.Name = rawStageResult.Name
result.Options = rawStageResult.Options
result.Success = rawStageResult.Success
result.Output = rawStageResult.Output
result.Metadata = metadata
return nil
}
func (cr *Result) Write(writer io.Writer) error {
if cr.Build == nil && len(cr.Stages) == 0 && cr.Assembler == nil {
fmt.Fprintf(writer, "The compose result is empty.\n")
}
if cr.Build != nil {
fmt.Fprintf(writer, "Build pipeline:\n")
for _, stage := range cr.Build.Stages {
fmt.Fprintf(writer, "Stage %s\n", stage.Name)
enc := json.NewEncoder(writer)
enc.SetIndent("", " ")
err := enc.Encode(stage.Options)
if err != nil {
return err
}
fmt.Fprintf(writer, "\nOutput:\n%s\n", stage.Output)
}
}
if len(cr.Stages) > 0 {
fmt.Fprintf(writer, "Stages:\n")
for _, stage := range cr.Stages {
fmt.Fprintf(writer, "Stage: %s\n", stage.Name)
enc := json.NewEncoder(writer)
enc.SetIndent("", " ")
err := enc.Encode(stage.Options)
if err != nil {
return err
}
fmt.Fprintf(writer, "\nOutput:\n%s\n", stage.Output)
}
}
if cr.Assembler != nil {
fmt.Fprintf(writer, "Assembler %s:\n", cr.Assembler.Name)
enc := json.NewEncoder(writer)
enc.SetIndent("", " ")
err := enc.Encode(cr.Assembler.Options)
if err != nil {
return err
}
fmt.Fprintf(writer, "\nOutput:\n%s\n", cr.Assembler.Output)
}
return nil
}
func (cr *Result) UnmarshalJSON(data []byte) error {
// NOTE(akoutsou) 1to2t: result format depends on the osbuild version; this
// unmarshaller tries to read both old and new versions
type resultAlias Result
crv1 := new(resultAlias)
dec := json.NewDecoder(bytes.NewReader(data))
dec.DisallowUnknownFields()
if err := dec.Decode(&crv1); err == nil {
*cr = Result(*crv1)
return nil
}
// try osbuild2 result object
crv2 := new(osbuild2.Result)
// NOTE: Using plain (non-strict) Unmarshal here. The format of the new
// osbuild output schema is not yet fixed and is likely to change, so
// disallowing unknown fields will likely cause failures in the near future.
if err := json.Unmarshal(data, &crv2); err != nil {
return err
}
cr.fromV2(crv2)
return nil
}
// Convert new OSBuild v2 format result into a v1 by copying the most useful
// values:
// - Compose success status
// - Output of Stages (Log) as flattened list of v1 StageResults
func (cr *Result) fromV2(crv2 *osbuild2.Result) {
cr.Success = crv2.Success
// Empty build and assembler results for new types of jobs
cr.Build = new(buildResult)
cr.Assembler = new(rawAssemblerResult)
// convert all stages logs from all pipelines into v1 StageResult objects
for pname, stages := range crv2.Log {
for idx, stage := range stages {
stageResult := StageResult{
// Create uniquely identifiable name for the stage:
// <pipeline name>:<stage index>-<stage type>
Name: fmt.Sprintf("%s:%d-%s", pname, idx, stage.Type),
Success: stage.Success,
Output: stage.Output,
}
cr.Stages = append(cr.Stages, stageResult)
}
}
}