-- ============================================================ -- CMDB Schema v1.0 — PostgreSQL -- ============================================================ -- Strategy: soft delete everywhere, audit trail via triggers, -- JSONB for extensible attributes, full indexing. -- ============================================================ BEGIN; -- ----------------------------------------------------------- -- 0. Extensions -- ----------------------------------------------------------- CREATE EXTENSION IF NOT EXISTS "uuid-ossp"; CREATE EXTENSION IF NOT EXISTS "pgcrypto"; -- ----------------------------------------------------------- -- 1. Custom ENUM types -- ----------------------------------------------------------- CREATE TYPE ci_status AS ENUM ( 'active', 'inactive', 'maintenance', 'deprecated', 'planned' ); CREATE TYPE relationship_type AS ENUM ( 'depends_on', 'connected_to', 'hosted_on', 'runs_on', 'manages', 'contains', 'part_of', 'related_to' ); CREATE TYPE change_action AS ENUM ( 'create', 'update', 'delete', 'restore', 'relationship_add', 'relationship_remove' ); CREATE TYPE user_role AS ENUM ('admin', 'editor', 'viewer'); -- ----------------------------------------------------------- -- 2. Locations -- ----------------------------------------------------------- CREATE TABLE locations ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), name TEXT NOT NULL UNIQUE, description TEXT, parent_id UUID REFERENCES locations(id) ON DELETE SET NULL, metadata JSONB DEFAULT '{}', created_at TIMESTAMPTZ NOT NULL DEFAULT now(), updated_at TIMESTAMPTZ NOT NULL DEFAULT now(), deleted_at TIMESTAMPTZ ); CREATE INDEX idx_locations_parent ON locations(parent_id) WHERE deleted_at IS NULL; CREATE INDEX idx_locations_metadata ON locations USING gin(metadata); -- ----------------------------------------------------------- -- 3. Users / Teams (owners) -- ----------------------------------------------------------- CREATE TABLE users ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), username TEXT NOT NULL UNIQUE, email TEXT NOT NULL UNIQUE, full_name TEXT, role user_role NOT NULL DEFAULT 'viewer', team TEXT, is_active BOOLEAN NOT NULL DEFAULT true, password_hash TEXT NOT NULL, created_at TIMESTAMPTZ NOT NULL DEFAULT now(), updated_at TIMESTAMPTZ NOT NULL DEFAULT now(), deleted_at TIMESTAMPTZ ); CREATE INDEX idx_users_role ON users(role) WHERE deleted_at IS NULL; CREATE INDEX idx_users_team ON users(team) WHERE deleted_at IS NULL AND team IS NOT NULL; -- ----------------------------------------------------------- -- 4. CI Classes (taxonomy) -- ----------------------------------------------------------- CREATE TABLE ci_classes ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), name TEXT NOT NULL UNIQUE, -- e.g. 'Server', 'NetworkDevice', 'Service' description TEXT, parent_id UUID REFERENCES ci_classes(id) ON DELETE SET NULL, icon TEXT, -- optional icon name for UI created_at TIMESTAMPTZ NOT NULL DEFAULT now(), updated_at TIMESTAMPTZ NOT NULL DEFAULT now(), deleted_at TIMESTAMPTZ ); CREATE INDEX idx_ci_classes_parent ON ci_classes(parent_id) WHERE deleted_at IS NULL; -- ----------------------------------------------------------- -- 5. CI Types (subtypes within a class) -- ----------------------------------------------------------- CREATE TABLE ci_types ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), class_id UUID NOT NULL REFERENCES ci_classes(id) ON DELETE CASCADE, name TEXT NOT NULL, -- e.g. 'PhysicalServer', 'VirtualMachine', 'Switch' description TEXT, created_at TIMESTAMPTZ NOT NULL DEFAULT now(), updated_at TIMESTAMPTZ NOT NULL DEFAULT now(), deleted_at TIMESTAMPTZ, UNIQUE(class_id, name) ); CREATE INDEX idx_ci_types_class ON ci_types(class_id) WHERE deleted_at IS NULL; -- ----------------------------------------------------------- -- 6. Attribute definitions (template per class) -- ----------------------------------------------------------- CREATE TABLE attributes ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), ci_class_id UUID NOT NULL REFERENCES ci_classes(id) ON DELETE CASCADE, name TEXT NOT NULL, -- e.g. 'cpu_cores', 'ram_gb' label TEXT NOT NULL, -- display label data_type TEXT NOT NULL DEFAULT 'text', -- text, integer, float, boolean, date, ip, json is_required BOOLEAN NOT NULL DEFAULT false, is_unique BOOLEAN NOT NULL DEFAULT false, default_value TEXT, validation_regex TEXT, created_at TIMESTAMPTZ NOT NULL DEFAULT now(), deleted_at TIMESTAMPTZ, UNIQUE(ci_class_id, name) ); CREATE INDEX idx_attributes_class ON attributes(ci_class_id) WHERE deleted_at IS NULL; -- ----------------------------------------------------------- -- 7. Owners (many-to-many: CI ↔ User) -- ----------------------------------------------------------- CREATE TABLE owners ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), ci_id UUID NOT NULL, user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE, role TEXT NOT NULL DEFAULT 'owner', -- owner, admin, responsible created_at TIMESTAMPTZ NOT NULL DEFAULT now(), UNIQUE(ci_id, user_id, role) ); -- ----------------------------------------------------------- -- 8. Configuration Items (the core entity) -- ----------------------------------------------------------- CREATE TABLE configuration_items ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), ci_type_id UUID NOT NULL REFERENCES ci_types(id) ON DELETE RESTRICT, name TEXT NOT NULL, description TEXT, status ci_status NOT NULL DEFAULT 'active', location_id UUID REFERENCES locations(id) ON DELETE SET NULL, serial_number TEXT, asset_tag TEXT, purchase_date DATE, warranty_expiry DATE, attributes JSONB DEFAULT '{}', -- extensible key-value store tags TEXT[] DEFAULT '{}', created_by UUID REFERENCES users(id), created_at TIMESTAMPTZ NOT NULL DEFAULT now(), updated_at TIMESTAMPTZ NOT NULL DEFAULT now(), deleted_at TIMESTAMPTZ, version INTEGER NOT NULL DEFAULT 1 ); CREATE INDEX idx_ci_type ON configuration_items(ci_type_id) WHERE deleted_at IS NULL; CREATE INDEX idx_ci_status ON configuration_items(status) WHERE deleted_at IS NULL; CREATE INDEX idx_ci_location ON configuration_items(location_id) WHERE deleted_at IS NULL; CREATE INDEX idx_ci_name ON configuration_items(name) WHERE deleted_at IS NULL; CREATE INDEX idx_ci_serial ON configuration_items(serial_number) WHERE deleted_at IS NULL AND serial_number IS NOT NULL; CREATE INDEX idx_ci_asset_tag ON configuration_items(asset_tag) WHERE deleted_at IS NULL AND asset_tag IS NOT NULL; CREATE INDEX idx_ci_tags ON configuration_items USING gin(tags) WHERE deleted_at IS NULL; CREATE INDEX idx_ci_attributes ON configuration_items USING gin(attributes) WHERE deleted_at IS NULL; -- Unique constraint on name + type for active items CREATE UNIQUE INDEX idx_ci_name_type_active ON configuration_items(name, ci_type_id) WHERE deleted_at IS NULL; -- ----------------------------------------------------------- -- 9. IP Addresses -- ----------------------------------------------------------- CREATE TABLE ip_addresses ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), ci_id UUID NOT NULL REFERENCES configuration_items(id) ON DELETE CASCADE, ip_address INET NOT NULL, subnet_mask INET, gateway INET, dns_servers INET[], is_primary BOOLEAN NOT NULL DEFAULT false, vlan_id INTEGER, dhcp_enabled BOOLEAN DEFAULT false, created_at TIMESTAMPTZ NOT NULL DEFAULT now(), updated_at TIMESTAMPTZ NOT NULL DEFAULT now(), deleted_at TIMESTAMPTZ ); CREATE INDEX idx_ip_address ON ip_addresses(ip_address) WHERE deleted_at IS NULL; CREATE INDEX idx_ip_ci ON ip_addresses(ci_id) WHERE deleted_at IS NULL; -- ----------------------------------------------------------- -- 10. Network Interfaces -- ----------------------------------------------------------- CREATE TABLE network_interfaces ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), ci_id UUID NOT NULL REFERENCES configuration_items(id) ON DELETE CASCADE, name TEXT NOT NULL, -- eth0, ens192, bond0 mac_address MACADDR, speed_mbps INTEGER, interface_type TEXT DEFAULT 'ethernet', -- ethernet, bond, bridge, vlan is_up BOOLEAN DEFAULT true, ip_address_id UUID REFERENCES ip_addresses(id) ON DELETE SET NULL, created_at TIMESTAMPTZ NOT NULL DEFAULT now(), updated_at TIMESTAMPTZ NOT NULL DEFAULT now(), deleted_at TIMESTAMPTZ ); CREATE INDEX idx_nic_ci ON network_interfaces(ci_id) WHERE deleted_at IS NULL; CREATE INDEX idx_nic_mac ON network_interfaces(mac_address) WHERE deleted_at IS NULL AND mac_address IS NOT NULL; -- ----------------------------------------------------------- -- 11. Hardware Details -- ----------------------------------------------------------- CREATE TABLE hardware_details ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), ci_id UUID NOT NULL REFERENCES configuration_items(id) ON DELETE CASCADE, manufacturer TEXT, model TEXT, cpu_model TEXT, cpu_cores INTEGER, ram_gb NUMERIC(10,2), storage_gb NUMERIC(10,2), storage_type TEXT, -- SSD, HDD, NVMe form_factor TEXT, -- 1U, 2U,塔式, 刀片 power_supply TEXT, bios_version TEXT, serial_number TEXT, specs JSONB DEFAULT '{}', -- extra hardware specs created_at TIMESTAMPTZ NOT NULL DEFAULT now(), updated_at TIMESTAMPTZ NOT NULL DEFAULT now(), deleted_at TIMESTAMPTZ ); CREATE UNIQUE INDEX idx_hw_ci ON hardware_details(ci_id) WHERE deleted_at IS NULL; -- ----------------------------------------------------------- -- 12. Software Instances -- ----------------------------------------------------------- CREATE TABLE software_instances ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), ci_id UUID NOT NULL REFERENCES configuration_items(id) ON DELETE CASCADE, name TEXT NOT NULL, vendor TEXT, version TEXT, license_key TEXT, license_type TEXT, -- open_source, commercial, trial install_path TEXT, config_path TEXT, port INTEGER, protocol TEXT, -- tcp, udp start_command TEXT, service_user TEXT, config JSONB DEFAULT '{}', created_at TIMESTAMPTZ NOT NULL DEFAULT now(), updated_at TIMESTAMPTZ NOT NULL DEFAULT now(), deleted_at TIMESTAMPTZ ); CREATE INDEX idx_sw_ci ON software_instances(ci_id) WHERE deleted_at IS NULL; CREATE INDEX idx_sw_name ON software_instances(name) WHERE deleted_at IS NULL; -- ----------------------------------------------------------- -- 13. CI Relationships (graph edges) -- ----------------------------------------------------------- CREATE TABLE ci_relationships ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), source_ci_id UUID NOT NULL REFERENCES configuration_items(id) ON DELETE CASCADE, target_ci_id UUID NOT NULL REFERENCES configuration_items(id) ON DELETE CASCADE, relationship relationship_type NOT NULL, description TEXT, metadata JSONB DEFAULT '{}', created_at TIMESTAMPTZ NOT NULL DEFAULT now(), deleted_at TIMESTAMPTZ, UNIQUE(source_ci_id, target_ci_id, relationship) ); CREATE INDEX idx_rel_source ON ci_relationships(source_ci_id) WHERE deleted_at IS NULL; CREATE INDEX idx_rel_target ON ci_relationships(target_ci_id) WHERE deleted_at IS NULL; CREATE INDEX idx_rel_type ON ci_relationships(relationship) WHERE deleted_at IS NULL; -- ----------------------------------------------------------- -- 14. Change Log (audit trail / versioning) -- ----------------------------------------------------------- CREATE TABLE changelog ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), ci_id UUID NOT NULL REFERENCES configuration_items(id) ON DELETE CASCADE, action change_action NOT NULL, changed_by UUID REFERENCES users(id), field_name TEXT, old_value TEXT, new_value TEXT, snapshot JSONB, -- full CI snapshot at time of change version INTEGER NOT NULL, comment TEXT, created_at TIMESTAMPTZ NOT NULL DEFAULT now() ); CREATE INDEX idx_changelog_ci ON changelog(ci_id); CREATE INDEX idx_changelog_action ON changelog(action); CREATE INDEX idx_changelog_created ON changelog(created_at); CREATE INDEX idx_changelog_version ON changelog(ci_id, version); -- ----------------------------------------------------------- -- 15. Audit Trigger Function -- ----------------------------------------------------------- CREATE OR REPLACE FUNCTION audit_trigger_func() RETURNS TRIGGER AS $$ BEGIN IF TG_OP = 'UPDATE' THEN INSERT INTO changelog (ci_id, action, field_name, old_value, new_value, version, snapshot) VALUES ( NEW.id, 'update', 'general', row_to_json(OLD)::text, row_to_json(NEW)::text, NEW.version, to_jsonb(NEW) ); NEW.updated_at = now(); NEW.version = OLD.version + 1; RETURN NEW; ELSIF TG_OP = 'DELETE' THEN INSERT INTO changelog (ci_id, action, snapshot, version) VALUES ( OLD.id, 'delete', to_jsonb(OLD), OLD.version ); RETURN OLD; END IF; RETURN NULL; END; $$ LANGUAGE plpgsql; CREATE TRIGGER trg_ci_audit AFTER UPDATE OR DELETE ON configuration_items FOR EACH ROW EXECUTE FUNCTION audit_trigger_func(); -- ----------------------------------------------------------- -- 16. Updated_at auto-trigger -- ----------------------------------------------------------- CREATE OR REPLACE FUNCTION update_updated_at() RETURNS TRIGGER AS $$ BEGIN NEW.updated_at = now(); RETURN NEW; END; $$ LANGUAGE plpgsql; CREATE TRIGGER trg_locations_updated BEFORE UPDATE ON locations FOR EACH ROW EXECUTE FUNCTION update_updated_at(); CREATE TRIGGER trg_users_updated BEFORE UPDATE ON users FOR EACH ROW EXECUTE FUNCTION update_updated_at(); CREATE TRIGGER trg_ci_classes_updated BEFORE UPDATE ON ci_classes FOR EACH ROW EXECUTE FUNCTION update_updated_at(); CREATE TRIGGER trg_ci_types_updated BEFORE UPDATE ON ci_types FOR EACH ROW EXECUTE FUNCTION update_updated_at(); CREATE TRIGGER trg_ip_updated BEFORE UPDATE ON ip_addresses FOR EACH ROW EXECUTE FUNCTION update_updated_at(); CREATE TRIGGER trg_nic_updated BEFORE UPDATE ON network_interfaces FOR EACH ROW EXECUTE FUNCTION update_updated_at(); CREATE TRIGGER trg_hw_updated BEFORE UPDATE ON hardware_details FOR EACH ROW EXECUTE FUNCTION update_updated_at(); CREATE TRIGGER trg_sw_updated BEFORE UPDATE ON software_instances FOR EACH ROW EXECUTE FUNCTION update_updated_at(); COMMIT;