// 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 } // ImageBuild represents a single image build inside a compose type ImageBuild struct { Id int `json:"id"` QueueStatus common.ImageBuildState `json:"queue_status"` ImageType common.ImageType `json:"image_type"` Manifest *osbuild.Manifest `json:"manifest"` Targets []*target.Target `json:"targets"` JobCreated time.Time `json:"job_created"` JobStarted time.Time `json:"job_started"` JobFinished time.Time `json:"job_finished"` Size uint64 `json:"size"` } // DeepCopy creates a copy of the ImageBuild structure func (ib *ImageBuild) DeepCopy() ImageBuild { var newManifestPtr *osbuild.Manifest = nil if ib.Manifest != nil { manifestCopy := *ib.Manifest newManifestPtr = &manifestCopy } var newTargets []*target.Target for _, t := range ib.Targets { newTarget := *t newTargets = append(newTargets, &newTarget) } // Create new image build struct return ImageBuild{ Id: ib.Id, QueueStatus: ib.QueueStatus, ImageType: ib.ImageType, Manifest: newManifestPtr, Targets: newTargets, JobCreated: ib.JobCreated, JobStarted: ib.JobStarted, JobFinished: ib.JobFinished, Size: ib.Size, } } func (ib *ImageBuild) GetLocalTargetOptions() *target.LocalTargetOptions { for _, t := range ib.Targets { switch options := t.Options.(type) { case *target.LocalTargetOptions: return options } } return nil } // 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 }