| new file mode 100644 |
| index 000000000..f0070cff2 |
| --- /dev/null |
| +++ b/internal/infraview/infraview.go |
| @@ -0,0 +1,118 @@ |
| +// Package infraview is what an infrastructure inventory IS, in terms no |
| +// cloud provider owns. It is deliberately free of every SDK: the AWS |
| +// implementation (internal/awsinv) imports this package, never the other |
| +// way round, so the console page that renders an inventory can be pointed |
| +// at a second provider by writing one file rather than by rewriting the |
| +// page. |
| +// |
| +// The wire shape here is written to a production bucket |
| +// (InventoryKey), so it obeys the repo's additive rule: every field is |
| +// optional, unknown fields parse, and an unknown Kind is carried through |
| +// as itself. A reader that drops what it does not recognise would make the |
| +// page LESS useful exactly where it is most useful — the resource nobody |
| +// wrote code for is the one worth seeing. |
| +package infraview |
| + |
| +import ( |
| + "context" |
| + "time" |
| +) |
| + |
| +// InventoryKey is where a deployment's inventory snapshot lives in its own |
| +// bucket. One object, overwritten in place: this is a current-state |
| +// snapshot, not a history, and a history would need a retention policy |
| +// nobody has asked for. |
| +const InventoryKey = "infra/inventory.json" |
| + |
| +// The kind vocabulary. Small and neutral on purpose — a provider maps its |
| +// own types into these and carries the truth in NativeType, so the page's |
| +// layout rules ("machines sit in the box layer") never have to learn a new |
| +// cloud's nouns. KindOther is not a failure: it is the honest home for a |
| +// discovered resource this vocabulary has no word for. |
| +const ( |
| + KindMachine = "machine" |
| + KindIP = "ip" |
| + KindDisk = "disk" |
| + KindFirewall = "firewall" |
| + KindNetwork = "network" |
| + KindBucket = "bucket" |
| + KindDNSZone = "dns_zone" |
| + KindDNSRecord = "dns_record" |
| + KindIdentity = "identity" |
| + KindAlarm = "alarm" |
| + KindCertificate = "certificate" |
| + KindOther = "other" |
| +) |
| + |
| +// Resource is one thing a provider is carrying. Attrs is stringly on |
| +// purpose: a typed struct per kind would need a migration every time a |
| +// provider exposes a new field, and this object must stay parseable by |
| +// readers written before that field existed. Keys the console renders are |
| +// documented where it reads them; keys it does not know are shown verbatim. |
| +type Resource struct { |
| + Provider string `json:"provider"` |
| + Kind string `json:"kind"` |
| + NativeType string `json:"native_type,omitempty"` |
| + ID string `json:"id"` |
| + Name string `json:"name,omitempty"` |
| + Region string `json:"region,omitempty"` |
| + Attrs map[string]string `json:"attrs,omitempty"` |
| + Tags map[string]string `json:"tags,omitempty"` |
| +} |
| + |
| +// Attr reads one attribute without the caller having to nil-check the map. |
| +func (r Resource) Attr(key string) string { |
| + if r.Attrs == nil { |
| + return "" |
| + } |
| + return r.Attrs[key] |
| +} |
| + |
| +// Inventory is one provider's estate at one moment. Partial names the reads |
| +// that FAILED, in words a person can act on ("security groups in eu-west-1: |
| +// throttled"). It exists because "no security groups" and "the security |
| +// group read failed" are different sentences and only one of them may be |
| +// rendered as a fact. |
| +type Inventory struct { |
| + Provider string `json:"provider"` |
| + Account string `json:"account,omitempty"` |
| + TakenAt time.Time `json:"taken_at"` |
| + Resources []Resource `json:"resources"` |
| + Partial []string `json:"partial,omitempty"` |
| +} |
| + |
| +// ByKind returns every resource of one kind, in inventory order. |
| +func (i Inventory) ByKind(kind string) []Resource { |
| + var out []Resource |
| + for _, r := range i.Resources { |
| + if r.Kind == kind { |
| + out = append(out, r) |
| + } |
| + } |
| + return out |
| +} |
| + |
| +// Find returns the first resource of kind whose attr equals value, or nil. |
| +// value == "" never matches: an empty attribute is absence, and matching on |
| +// it would join every machine with no public IP to every record with no |
| +// value. |
| +func (i Inventory) Find(kind, attr, value string) *Resource { |
| + if value == "" { |
| + return nil |
| + } |
| + for idx := range i.Resources { |
| + if i.Resources[idx].Kind == kind && i.Resources[idx].Attr(attr) == value { |
| + return &i.Resources[idx] |
| + } |
| + } |
| + return nil |
| +} |
| + |
| +// Provider is one cloud (or one bare-metal estate) that can describe |
| +// itself. Implementations return construction-class failures as an error — |
| +// no credential, no reachable API — and everything else through |
| +// Inventory.Partial, because a page that can show eleven resource classes |
| +// must show eleven. |
| +type Provider interface { |
| + Inventory(ctx context.Context) (Inventory, error) |
| +} |