package seeds import ( "bytes" "context" "encoding/json" "errors" "fmt" "io" "os" "github.com/carlosframework/platform/internal/release" ) // LegacyOwnersKey is the operator assignment of legacy route names // (seeded-instances spec ยง6.4, codex 9): which account and app a route // filed under the operator account belongs to. const LegacyOwnersKey = "control/legacy-route-owners.json" // legacyOwnersVersion is the only file version this reader understands. const legacyOwnersVersion = 1 // LegacyOwner is the account and app a legacy route name is assigned to. type LegacyOwner struct { Account string `json:"account"` App string `json:"app"` } // LegacyOwners maps a route name, as filed under the operator account, to // the one account and app it belongs to. type LegacyOwners struct { V int `json:"v"` Names map[string]LegacyOwner `json:"names"` } // ReadLegacyOwners reads the assignments. It is an authority check, so it // fails closed: an absent file is an empty set (nothing is assigned), but a // file that is present and unreadable, malformed, of an unknown version or // carrying an incomplete entry is an error and no owners, never "no // assignment". The etag is "" for an absent file. func ReadLegacyOwners(ctx context.Context, s release.Store) (*LegacyOwners, string, error) { body, etag, err := s.Get(ctx, LegacyOwnersKey) if errors.Is(err, os.ErrNotExist) { return &LegacyOwners{V: legacyOwnersVersion, Names: map[string]LegacyOwner{}}, "", nil } if err != nil { return nil, "", fmt.Errorf("seeds: read legacy route owners: %w", err) } dec := json.NewDecoder(bytes.NewReader(body)) dec.DisallowUnknownFields() var o LegacyOwners if err := dec.Decode(&o); err != nil { return nil, "", fmt.Errorf("seeds: parse legacy route owners: %w", err) } if _, err := dec.Token(); err != io.EOF { return nil, "", errors.New("seeds: parse legacy route owners: data after the object") } if o.V != legacyOwnersVersion { return nil, "", fmt.Errorf("seeds: legacy route owners: unknown version %d", o.V) } if o.Names == nil { o.Names = map[string]LegacyOwner{} } for name, ow := range o.Names { if name == "" || ow.Account == "" || ow.App == "" { return nil, "", fmt.Errorf("seeds: legacy route owners: incomplete entry %q", name) } } return &o, etag, nil } // Lookup returns the owner assigned to name. A name that is not in the set // is not assigned. func (o *LegacyOwners) Lookup(name string) (LegacyOwner, bool) { if o == nil { return LegacyOwner{}, false } v, ok := o.Names[name] return v, ok }