-- Migration : architecture intervention complète (Teams + PCT + workflow) -- - teams_channels (canaux Teams configurés) -- - server_clusters (groupes avec ordre de redémarrage) -- - contacts enrichis (teams_upn, phone, is_pct_member) -- - applications enrichies (teams_channel_id, pct_required, pct_email override) -- - servers enrichis (FK contacts, teams, pct, snap policy, hooks pre/post, -- cluster + cluster_order) -- - patch_planning_import_rows enrichis (workflow intervention complet) -- Idempotent — exécutable plusieurs fois. -- ─── 1) Nouvelles tables ────────────────────────────────────── CREATE TABLE IF NOT EXISTS public.teams_channels ( id SERIAL PRIMARY KEY, name text NOT NULL, webhook_url text NOT NULL, description text, is_active boolean NOT NULL DEFAULT true, is_default boolean NOT NULL DEFAULT false, created_at timestamptz NOT NULL DEFAULT now() ); CREATE TABLE IF NOT EXISTS public.server_clusters ( id SERIAL PRIMARY KEY, name text NOT NULL UNIQUE, description text, reboot_strategy text NOT NULL DEFAULT 'sequential', -- sequential | parallel is_active boolean NOT NULL DEFAULT true, created_at timestamptz NOT NULL DEFAULT now() ); -- ─── 2) Enrichir contacts ───────────────────────────────────── ALTER TABLE public.contacts ADD COLUMN IF NOT EXISTS teams_upn text, ADD COLUMN IF NOT EXISTS phone text, ADD COLUMN IF NOT EXISTS is_pct_member boolean DEFAULT false; -- ─── 3) Enrichir applications ──────────────────────────────── ALTER TABLE public.applications ADD COLUMN IF NOT EXISTS teams_channel_id integer REFERENCES public.teams_channels(id) ON DELETE SET NULL, ADD COLUMN IF NOT EXISTS pct_required boolean DEFAULT false, ADD COLUMN IF NOT EXISTS pct_email text; -- pct_email : override si différent du défaut global (pct.reims@sanef.com) -- ─── 4) Enrichir servers ───────────────────────────────────── ALTER TABLE public.servers ADD COLUMN IF NOT EXISTS responsable_domaine_contact_id integer REFERENCES public.contacts(id) ON DELETE SET NULL, ADD COLUMN IF NOT EXISTS referent_technique_contact_id integer REFERENCES public.contacts(id) ON DELETE SET NULL, ADD COLUMN IF NOT EXISTS valideur_ra_contact_id integer REFERENCES public.contacts(id) ON DELETE SET NULL, ADD COLUMN IF NOT EXISTS teams_channel_id integer REFERENCES public.teams_channels(id) ON DELETE SET NULL, ADD COLUMN IF NOT EXISTS pct_required boolean DEFAULT false, ADD COLUMN IF NOT EXISTS requires_snapshot boolean DEFAULT true, ADD COLUMN IF NOT EXISTS snap_can_be_skipped boolean DEFAULT false, ADD COLUMN IF NOT EXISTS pre_patch_cmd text, ADD COLUMN IF NOT EXISTS post_patch_cmd text, ADD COLUMN IF NOT EXISTS cluster_id integer REFERENCES public.server_clusters(id) ON DELETE SET NULL, ADD COLUMN IF NOT EXISTS cluster_order integer; -- Backfill : physiques → requires_snapshot=false (snap vCenter pas applicable) UPDATE public.servers SET requires_snapshot = false WHERE LOWER(COALESCE(machine_type::text, '')) LIKE '%physic%'; -- ─── 5) Enrichir patch_planning_import_rows (workflow intervention) ─ ALTER TABLE public.patch_planning_import_rows ADD COLUMN IF NOT EXISTS intervention_started_at timestamptz, ADD COLUMN IF NOT EXISTS intervention_finished_at timestamptz, ADD COLUMN IF NOT EXISTS validated_at timestamptz, ADD COLUMN IF NOT EXISTS validated_by_contact_id integer REFERENCES public.contacts(id) ON DELETE SET NULL, ADD COLUMN IF NOT EXISTS validation_status text, -- 'ok' | 'ko' | 'en_attente' | NULL ADD COLUMN IF NOT EXISTS validation_note text, ADD COLUMN IF NOT EXISTS snap_skipped boolean DEFAULT false, ADD COLUMN IF NOT EXISTS snap_skip_reason text, ADD COLUMN IF NOT EXISTS patch_forced boolean DEFAULT false, ADD COLUMN IF NOT EXISTS patch_forced_reason text, ADD COLUMN IF NOT EXISTS teams_reminder_sent_at timestamptz, ADD COLUMN IF NOT EXISTS teams_start_sent_at timestamptz, ADD COLUMN IF NOT EXISTS teams_end_sent_at timestamptz, ADD COLUMN IF NOT EXISTS pct_mail_sent_at timestamptz, ADD COLUMN IF NOT EXISTS rollback_done_at timestamptz, ADD COLUMN IF NOT EXISTS rollback_method text; -- rollback_method : 'yum_undo' | 'snapshot_restore' | 'commvault' | 'manual' -- ─── 6) Index ───────────────────────────────────────────────── CREATE INDEX IF NOT EXISTS idx_pp_rows_validation_status ON public.patch_planning_import_rows(validation_status) WHERE validation_status IS NOT NULL; CREATE INDEX IF NOT EXISTS idx_pp_rows_intervention_started ON public.patch_planning_import_rows(intervention_started_at) WHERE intervention_started_at IS NOT NULL; CREATE INDEX IF NOT EXISTS idx_servers_cluster ON public.servers(cluster_id, cluster_order) WHERE cluster_id IS NOT NULL; CREATE INDEX IF NOT EXISTS idx_servers_resp_domaine ON public.servers(responsable_domaine_contact_id) WHERE responsable_domaine_contact_id IS NOT NULL; CREATE INDEX IF NOT EXISTS idx_servers_referent ON public.servers(referent_technique_contact_id) WHERE referent_technique_contact_id IS NOT NULL; CREATE INDEX IF NOT EXISTS idx_servers_valideur ON public.servers(valideur_ra_contact_id) WHERE valideur_ra_contact_id IS NOT NULL; -- ─── 7) GRANT pour le user applicatif ───────────────────────── GRANT SELECT, INSERT, UPDATE, DELETE ON public.teams_channels TO patchcenter; GRANT SELECT, INSERT, UPDATE, DELETE ON public.server_clusters TO patchcenter; GRANT USAGE, SELECT ON SEQUENCE public.teams_channels_id_seq TO patchcenter; GRANT USAGE, SELECT ON SEQUENCE public.server_clusters_id_seq TO patchcenter; -- ─── Note ───────────────────────────────────────────────────── -- Le mail PCT global ('pct.reims@sanef.com') sera configuré via l'UI -- Settings (M5) car app_secrets utilise un chiffrement Fernet — on ne peut -- pas l'INSERT en clair via SQL.