package host import ( "context" "os" "path/filepath" "github.com/carlosframework/platform/internal/hostclaim" "github.com/carlosframework/platform/internal/hostguard" "github.com/carlosframework/platform/internal/provision" "github.com/carlosframework/platform/internal/release" "github.com/carlosframework/platform/internal/rootstate" ) type generationRecord struct { Key, ETag string Record provision.Record } func generationRequiredInventory(ctx context.Context, store release.Store, snapshot generationInventory, fresh func(hostguard.RootInstallation) error) (generationInventory, error) { var zero generationInventory if store == nil { return zero, os.ErrPermission } retirements, err := generationRetirements(ctx, snapshot) if err != nil { return zero, err } snapshot.Records = nil for _, view := range snapshot.Installations { if err := ctx.Err(); err != nil { return zero, err } in := view.Installation if _, err := inventoryTimerNames(in.Artifacts); err != nil { return zero, err } if in.Record != provision.RecordKey(in.Account, in.App, in.Host) { return zero, os.ErrPermission } retired := false for _, old := range view.Retired { if old.Incarnation == in.Incarnation && old.Stamp == in.Stamp { retired = true } } if retired { for i, payload := range snapshot.Payloads { if payload.Inventory.Host == in.Host && len(payload.Inventory.Entries) != 0 && retirements[i] == "" { return zero, os.ErrPermission } } continue } rec, etag, err := provision.GetRecordETag(ctx, store, in.Account, in.App, in.Host) if err != nil { return zero, err } if rec.Schema != 1 || !release.ValidAccount(rec.Account) || !release.ValidApp(rec.App) || provision.ValidateShape(*rec) != nil { return zero, os.ErrPermission } inc, err := hostclaim.Incarnation(rec.Account, rec.App, rec.Host, rec.CreatedAt) if err != nil || etag == "" || inc != in.Incarnation { return zero, os.ErrPermission } snapshot.Records = append(snapshot.Records, generationRecord{Key: in.Record, ETag: etag, Record: *rec}) } return generationLocalObligations(ctx, snapshot, fresh) } func generationLocalObligations(ctx context.Context, snapshot generationInventory, fresh func(hostguard.RootInstallation) error) (generationInventory, error) { var zero generationInventory snapshot.Unlaunched = map[string]string{} installs := map[string]hostguard.FenceView{} for _, view := range snapshot.Installations { installs[view.Host] = view } if err := generationPayloadBindings(snapshot.Payloads, installs); err != nil { return zero, err } if err := generationRetainedTimers(snapshot); err != nil { return zero, err } for _, view := range snapshot.Installations { if err := ctx.Err(); err != nil { return zero, err } in := view.Installation if !release.ValidAccount(in.Account) || !release.ValidApp(in.App) || in.Record != provision.RecordKey(in.Account, in.App, in.Host) { return zero, os.ErrPermission } if _, err := hostclaim.Key(in.Host); err != nil { return zero, err } unlaunched, err := generationUnlaunched(view, snapshot, fresh) if err != nil { return zero, err } if unlaunched { snapshot.Unlaunched[in.Host] = rootHistoryInstallation(in) } for _, record := range snapshot.Records { if record.Key == in.Record && record.Record.DerivesPaths() { in.Artifacts.StateDirectory = true } } for _, p := range snapshot.Payloads { payload := p.Inventory if payload.Host != in.Host { continue } required := len(payload.Entries) > 0 || !in.Empty || payload.InstallationStamp != in.Stamp for _, launch := range snapshot.Launches { t := launch.Target if t.Host == payload.Host && t.Incarnation == payload.Incarnation && t.Stamp == payload.InstallationStamp { required = true } } if !required { // Retained predecessors are checked below as nonempty obligations. // Without one, an empty payload needs root's creation evidence. if len(view.Retired) == 0 { if fresh == nil { return zero, os.ErrPermission } if err := fresh(in); err != nil { return zero, err } } continue } state, err := filepath.Rel(dataDir(), provision.InstanceDir(dataDir(), in.Host)) if err != nil || !filepath.IsLocal(state) || state == "." { return zero, os.ErrPermission } routes, directories := 0, 0 for _, entry := range payload.Entries { if entry.Kind == "route" { if entry.ManagedRoot != "registry" || entry.RelativePath != in.Host || entry.RowIdentity == "" { return zero, os.ErrPermission } routes++ } if entry.Kind == "path" && entry.ManagedRoot == "data" && entry.RelativePath == state { if entry.Type != 0040000 || entry.Device == 0 || entry.Inode == 0 { return zero, os.ErrPermission } directories++ } } if routes != 1 { return zero, os.ErrPermission } if in.Artifacts.StateDirectory && directories != 1 { if directories != 0 || payload.InstallationStamp != in.Stamp || len(view.Retired) != 0 || !unlaunched { return zero, os.ErrPermission } if _, err := os.Lstat(filepath.Join(dataDir(), state)); !os.IsNotExist(err) { return zero, os.ErrPermission } } } } return snapshot, ctx.Err() } func generationUnlaunched(view hostguard.FenceView, snapshot generationInventory, fresh func(hostguard.RootInstallation) error) (bool, error) { if fresh == nil || view.Installation.Empty { return false, nil } for _, launch := range snapshot.Launches { if launch.Target.Host == view.Host { return false, nil } } in := view.Installation for _, old := range view.Retired { if old.Incarnation == in.Incarnation && old.Stamp < in.Stamp { in.Stamp = old.Stamp } } if err := fresh(in); err != nil { return false, err } return true, nil } func productionGenerationCreation(in hostguard.RootInstallation) error { state, err := rootstate.Open(release.RootHistoryDir, false, false) if err != nil { return err } defer state.Close() fresh, err := (&release.RootHistory{Store: state}).FreshInstallation(release.HistoryBinding{Host: in.Host, Account: in.Account, App: in.App}, rootHistoryInstallation(in)) if err != nil { return err } if !fresh { return os.ErrPermission } return nil }