package hostactors import ( "context" "encoding/json" "github.com/carlosframework/platform/internal/hostfloor" "github.com/carlosframework/platform/internal/release" "time" ) // PublishFloor is called by the approved release workflow after the initial // deployment inventory has been reconciled and every guarded launch path // installed. It never discovers membership from reports. func (d Directory) PublishFloor(ctx context.Context, sg release.Signer, floor hostfloor.Floor, e Evidence, now time.Time) error { o, err := d.Read(ctx) if err != nil { return err } if len(o.Roster.Boxes) == 0 || len(o.Roster.Consoles) == 0 || len(o.Roster.Holds) != 0 || len(o.Roster.Activations) != 0 { return ErrRoster } raw, err := json.Marshal(floor) if err != nil { return err } prospective := hostfloor.Verified{Floor: floor, Digest: hostfloor.Digest(raw)} if o.Roster.Publication == "" { next := Clone(o.Roster) next.Publication = "preparing" next.FloorSHA256 = prospective.Digest o, err = d.Replace(ctx, sg, o, next) if err != nil { return err } } else if o.Roster.FloorSHA256 != prospective.Digest { return ErrRoster } for ref, b := range o.Roster.Boxes { // Initial publication requires every enrollment. A retained quarantine // is resolved by the activation runbook rather than skipped here. if b.State != "active" { return ErrRoster } r, err := e.BoxReport(ctx, ref, b) if err != nil { return err } if err = VerifyReport(ctx, e, d.PublicKey, prospective, r, b.Enrollment, b.Cloud.InstanceID, now); err != nil { return err } } for id, c := range o.Roster.Consoles { r, err := e.ConsoleReport(ctx, id, c) if err != nil { return err } if _, err := verifyCompute(ctx, o, e, id, c, r); err != nil { return err } if err = VerifyReport(ctx, e, d.PublicKey, prospective, r, c.Enrollment, id, now); err != nil { return err } } // An inventory move during the scan is a failed publication attempt. fresh, err := d.Read(ctx) if err != nil { return err } if fresh.ETag != o.ETag { return ErrRoster } if err = hostfloor.Publish(ctx, d.Store, sg, d.Bucket, floor); err != nil { return err } next := Clone(fresh.Roster) next.Publication = "published" _, err = d.Replace(ctx, sg, fresh, next) return err }