compose: refactor compose related structure into its own package
The store package is getting too big and convoluted, this new package will help with separation of the compose logic from our current implementation of the store. The current implementation will be removed in following commits.
This commit is contained in:
parent
62d186cd1b
commit
eb6c0f6fce
2 changed files with 297 additions and 0 deletions
172
internal/compose/compose.go
Normal file
172
internal/compose/compose.go
Normal file
|
|
@ -0,0 +1,172 @@
|
|||
// Package compose encapsulates the concept of a compose. It is a separate module from common, because it includes
|
||||
// target which in turn includes common and thus it would create a cyclic dependency, which is forbidden in golang.
|
||||
package compose
|
||||
|
||||
import (
|
||||
"github.com/osbuild/osbuild-composer/internal/blueprint"
|
||||
"github.com/osbuild/osbuild-composer/internal/common"
|
||||
"github.com/osbuild/osbuild-composer/internal/pipeline"
|
||||
"github.com/osbuild/osbuild-composer/internal/target"
|
||||
"time"
|
||||
)
|
||||
|
||||
type StateTransitionError struct {
|
||||
message string
|
||||
}
|
||||
|
||||
func (ste *StateTransitionError) Error() string {
|
||||
return ste.message
|
||||
}
|
||||
|
||||
// Image represents the image resulting from a compose.
|
||||
type Image struct {
|
||||
Path string
|
||||
Mime string
|
||||
Size uint64
|
||||
}
|
||||
|
||||
// ImageBuild represents a single image build inside a compose
|
||||
type ImageBuild struct {
|
||||
Id int `json:"id"`
|
||||
Distro common.Distribution `json:"distro"`
|
||||
QueueStatus common.ImageBuildState `json:"queue_status"`
|
||||
ImageType common.ImageType `json:"image_type"`
|
||||
Pipeline *pipeline.Pipeline `json:"pipeline"`
|
||||
Targets []*target.Target `json:"targets"`
|
||||
JobCreated time.Time `json:"job_created"`
|
||||
JobStarted time.Time `json:"job_started"`
|
||||
JobFinished time.Time `json:"job_finished"`
|
||||
Image *Image `json:"image"`
|
||||
Size uint64 `json:"size"`
|
||||
}
|
||||
|
||||
// DeepCopy creates a copy of the ImageBuild structure
|
||||
func (ib *ImageBuild) DeepCopy() ImageBuild {
|
||||
var newImagePtr *Image = nil
|
||||
if ib.Image != nil {
|
||||
imageCopy := *ib.Image
|
||||
newImagePtr = &imageCopy
|
||||
}
|
||||
var newPipelinePtr *pipeline.Pipeline = nil
|
||||
if ib.Pipeline != nil {
|
||||
pipelineCopy := *ib.Pipeline
|
||||
newPipelinePtr = &pipelineCopy
|
||||
}
|
||||
var newTargets []*target.Target
|
||||
for _, t := range ib.Targets {
|
||||
newTarget := *t
|
||||
newTargets = append(newTargets, &newTarget)
|
||||
}
|
||||
// Create new image build struct
|
||||
return ImageBuild{
|
||||
Distro: ib.Distro,
|
||||
QueueStatus: ib.QueueStatus,
|
||||
ImageType: ib.ImageType,
|
||||
Pipeline: newPipelinePtr,
|
||||
Targets: newTargets,
|
||||
JobCreated: ib.JobCreated,
|
||||
JobStarted: ib.JobStarted,
|
||||
JobFinished: ib.JobFinished,
|
||||
Image: newImagePtr,
|
||||
}
|
||||
}
|
||||
|
||||
// A Compose represent the task of building a set of images from a single blueprint.
|
||||
// It contains all the information necessary to generate the inputs for the job, as
|
||||
// well as the job's state.
|
||||
type Compose struct {
|
||||
Blueprint *blueprint.Blueprint `json:"blueprint"`
|
||||
ImageBuilds []ImageBuild `json:"image_builds"`
|
||||
}
|
||||
|
||||
// DeepCopy creates a copy of the Compose structure
|
||||
func (c *Compose) DeepCopy() Compose {
|
||||
var newBpPtr *blueprint.Blueprint = nil
|
||||
if c.Blueprint != nil {
|
||||
bpCopy := *c.Blueprint
|
||||
newBpPtr = &bpCopy
|
||||
}
|
||||
newImageBuilds := []ImageBuild{}
|
||||
for _, ib := range c.ImageBuilds {
|
||||
newImageBuilds = append(newImageBuilds, ib.DeepCopy())
|
||||
}
|
||||
return Compose{
|
||||
Blueprint: newBpPtr,
|
||||
ImageBuilds: newImageBuilds,
|
||||
}
|
||||
}
|
||||
|
||||
func anyImageBuild(fn func(common.ImageBuildState) bool, list []common.ImageBuildState) bool {
|
||||
acc := false
|
||||
for _, i := range list {
|
||||
if fn(i) {
|
||||
acc = true
|
||||
}
|
||||
}
|
||||
return acc
|
||||
}
|
||||
|
||||
func allImageBuilds(fn func(common.ImageBuildState) bool, list []common.ImageBuildState) bool {
|
||||
acc := true
|
||||
for _, i := range list {
|
||||
if !fn(i) {
|
||||
acc = false
|
||||
}
|
||||
}
|
||||
return acc
|
||||
}
|
||||
|
||||
// GetState returns a state of the whole compose which is derived from the states of
|
||||
// individual image builds inside the compose
|
||||
func (c *Compose) GetState() common.ComposeState {
|
||||
var imageBuildsStates []common.ImageBuildState
|
||||
for _, ib := range c.ImageBuilds {
|
||||
imageBuildsStates = append(imageBuildsStates, ib.QueueStatus)
|
||||
}
|
||||
// In case all states are the same
|
||||
if allImageBuilds(func(ib common.ImageBuildState) bool { return ib == common.IBWaiting }, imageBuildsStates) {
|
||||
return common.CWaiting
|
||||
}
|
||||
if allImageBuilds(func(ib common.ImageBuildState) bool { return ib == common.IBFinished }, imageBuildsStates) {
|
||||
return common.CFinished
|
||||
}
|
||||
if allImageBuilds(func(ib common.ImageBuildState) bool { return ib == common.IBFailed }, imageBuildsStates) {
|
||||
return common.CFailed
|
||||
}
|
||||
// In case the states are mixed
|
||||
// TODO: can this condition be removed because it is already covered by the default?
|
||||
if anyImageBuild(func(ib common.ImageBuildState) bool { return ib == common.IBRunning }, imageBuildsStates) {
|
||||
return common.CRunning
|
||||
}
|
||||
if allImageBuilds(func(ib common.ImageBuildState) bool { return ib == common.IBFailed || ib == common.IBFinished }, imageBuildsStates) {
|
||||
return common.CFailed
|
||||
}
|
||||
// Default value
|
||||
return common.CRunning
|
||||
}
|
||||
|
||||
// UpdateState changes a state of a single image build inside the Compose
|
||||
func (c *Compose) UpdateState(imageBuildId int, newState common.ImageBuildState) error {
|
||||
switch newState {
|
||||
case common.IBWaiting:
|
||||
return &StateTransitionError{"image build cannot be moved into waiting state"}
|
||||
case common.IBRunning:
|
||||
if c.ImageBuilds[imageBuildId].QueueStatus == common.IBWaiting || c.ImageBuilds[imageBuildId].QueueStatus == common.IBRunning {
|
||||
c.ImageBuilds[imageBuildId].QueueStatus = newState
|
||||
} else {
|
||||
return &StateTransitionError{"only waiting image build can be transitioned into running state"}
|
||||
}
|
||||
case common.IBFinished, common.IBFailed:
|
||||
if c.ImageBuilds[imageBuildId].QueueStatus == common.IBRunning {
|
||||
c.ImageBuilds[imageBuildId].QueueStatus = newState
|
||||
for _, t := range c.ImageBuilds[imageBuildId].Targets {
|
||||
t.Status = newState
|
||||
}
|
||||
} else {
|
||||
return &StateTransitionError{"only running image build can be transitioned into finished or failed state"}
|
||||
}
|
||||
default:
|
||||
return &StateTransitionError{"invalid state"}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
125
internal/compose/compose_test.go
Normal file
125
internal/compose/compose_test.go
Normal file
|
|
@ -0,0 +1,125 @@
|
|||
package compose
|
||||
|
||||
import (
|
||||
"github.com/osbuild/osbuild-composer/internal/common"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestGetState(t *testing.T) {
|
||||
cases := []struct{
|
||||
compose Compose
|
||||
expecedStatus common.ComposeState
|
||||
}{
|
||||
{
|
||||
compose: Compose{
|
||||
ImageBuilds: []ImageBuild{
|
||||
{QueueStatus: common.IBWaiting},
|
||||
},
|
||||
},
|
||||
expecedStatus: common.CWaiting,
|
||||
},
|
||||
{
|
||||
compose: Compose{
|
||||
ImageBuilds: []ImageBuild{
|
||||
{QueueStatus: common.IBRunning},
|
||||
},
|
||||
},
|
||||
expecedStatus: common.CRunning,
|
||||
},
|
||||
{
|
||||
compose: Compose{
|
||||
ImageBuilds: []ImageBuild{
|
||||
{QueueStatus: common.IBFailed},
|
||||
},
|
||||
},
|
||||
expecedStatus: common.CFailed,
|
||||
},
|
||||
{
|
||||
compose: Compose{
|
||||
ImageBuilds: []ImageBuild{
|
||||
{QueueStatus: common.IBFinished},
|
||||
},
|
||||
},
|
||||
expecedStatus: common.CFinished,
|
||||
},
|
||||
{
|
||||
compose: Compose{
|
||||
ImageBuilds: []ImageBuild{
|
||||
{QueueStatus: common.IBWaiting},
|
||||
{QueueStatus: common.IBWaiting},
|
||||
},
|
||||
},
|
||||
expecedStatus: common.CWaiting,
|
||||
},
|
||||
{
|
||||
compose: Compose{
|
||||
ImageBuilds: []ImageBuild{
|
||||
{QueueStatus: common.IBWaiting},
|
||||
{QueueStatus: common.IBRunning},
|
||||
},
|
||||
},
|
||||
expecedStatus: common.CRunning,
|
||||
},
|
||||
{
|
||||
compose: Compose{
|
||||
ImageBuilds: []ImageBuild{
|
||||
{QueueStatus: common.IBRunning},
|
||||
{QueueStatus: common.IBRunning},
|
||||
},
|
||||
},
|
||||
expecedStatus: common.CRunning,
|
||||
},
|
||||
{
|
||||
compose: Compose{
|
||||
ImageBuilds: []ImageBuild{
|
||||
{QueueStatus: common.IBRunning},
|
||||
{QueueStatus: common.IBFailed},
|
||||
},
|
||||
},
|
||||
expecedStatus: common.CRunning,
|
||||
},
|
||||
{
|
||||
compose: Compose{
|
||||
ImageBuilds: []ImageBuild{
|
||||
{QueueStatus: common.IBWaiting},
|
||||
{QueueStatus: common.IBFailed},
|
||||
},
|
||||
},
|
||||
expecedStatus: common.CRunning,
|
||||
},
|
||||
{
|
||||
compose: Compose{
|
||||
ImageBuilds: []ImageBuild{
|
||||
{QueueStatus: common.IBFailed},
|
||||
{QueueStatus: common.IBFailed},
|
||||
},
|
||||
},
|
||||
expecedStatus: common.CFailed,
|
||||
},
|
||||
{
|
||||
compose: Compose{
|
||||
ImageBuilds: []ImageBuild{
|
||||
{QueueStatus: common.IBFinished},
|
||||
{QueueStatus: common.IBFinished},
|
||||
},
|
||||
},
|
||||
expecedStatus: common.CFinished,
|
||||
},
|
||||
{
|
||||
compose: Compose{
|
||||
ImageBuilds: []ImageBuild{
|
||||
{QueueStatus: common.IBFinished},
|
||||
{QueueStatus: common.IBFailed},
|
||||
},
|
||||
},
|
||||
expecedStatus: common.CFailed,
|
||||
},
|
||||
}
|
||||
for n, c := range cases {
|
||||
got := c.compose.GetState()
|
||||
wanted := c.expecedStatus
|
||||
if got != wanted {
|
||||
t.Error("Compose", n, "should be in", wanted.ToString(), "state, but it is:", got.ToString())
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue