debian-forge-composer/internal/compose/compose.go
Tom Gundersen d133454d91 pipeline: rename package to osbuild
Rename the package from `pipeline` to `osbuild` to reflect that it
will no longer be specific to pipelines, but rather covers all
osbuild datatypes.

Signed-off-by: Tom Gundersen <teg@jklm.no>
2020-02-14 14:43:27 +01:00

183 lines
5.7 KiB
Go

// 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 (
"time"
"github.com/osbuild/osbuild-composer/internal/blueprint"
"github.com/osbuild/osbuild-composer/internal/common"
"github.com/osbuild/osbuild-composer/internal/osbuild"
"github.com/osbuild/osbuild-composer/internal/target"
)
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 *osbuild.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 *osbuild.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,
}
}
func (ib *ImageBuild) HasLocalTarget() bool {
for _, t := range ib.Targets {
if _, ok := t.Options.(*target.LocalTargetOptions); ok {
return true
}
}
return false
}
// 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
}