// 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) }