diff --git a/etc/schema/mysql-upgrades/2.11.0.sql b/etc/schema/mysql-upgrades/2.11.0.sql index d55d563d7..1a8c0b4d6 100644 --- a/etc/schema/mysql-upgrades/2.11.0.sql +++ b/etc/schema/mysql-upgrades/2.11.0.sql @@ -14,3 +14,23 @@ ALTER TABLE `icingaweb_user_preference` MODIFY COLUMN `username` varchar(254) COLLATE utf8mb4_unicode_ci NOT NULL, MODIFY COLUMN `section` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL, MODIFY COLUMN `name` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL; + +CREATE TABLE `icingaweb_config_scope`( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT, + `module` varchar(254) NOT NULL DEFAULT 'default', + `type` varchar(64) NOT NULL, + `name` varchar(254) COLLATE utf8mb4_unicode_ci NOT NULL, + `hash` binary(20) NOT NULL COMMENT 'sha1(all option tuples)', + PRIMARY KEY (`id`), + UNIQUE KEY `idx_module_type_name` (`module`, `type`, `name`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; + +CREATE TABLE `icingaweb_config_option`( + `scope_id` int(10) unsigned NOT NULL, + `name` varchar(254) NOT NULL, + `value` text DEFAULT NULL, + UNIQUE KEY `idx_scope_id_name` (`scope_id`, `name`), + CONSTRAINT `fk_scope_id_config_scope` FOREIGN KEY (`scope_id`) + REFERENCES `icingaweb_config_scope` (`id`) + ON DELETE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; diff --git a/etc/schema/mysql.schema.sql b/etc/schema/mysql.schema.sql index 18d9b2b83..ea6158165 100644 --- a/etc/schema/mysql.schema.sql +++ b/etc/schema/mysql.schema.sql @@ -52,3 +52,23 @@ CREATE TABLE `icingaweb_rememberme`( mtime timestamp NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (id) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin; + +CREATE TABLE `icingaweb_config_scope`( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT, + `module` varchar(254) NOT NULL DEFAULT 'default', + `type` varchar(64) NOT NULL, + `name` varchar(254) COLLATE utf8mb4_unicode_ci NOT NULL, + `hash` binary(20) NOT NULL COMMENT 'sha1(all option tuples)', + PRIMARY KEY (`id`), + UNIQUE KEY `idx_module_type_name` (`module`, `type`, `name`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; + +CREATE TABLE `icingaweb_config_option`( + `scope_id` int(10) unsigned NOT NULL, + `name` varchar(254) NOT NULL, + `value` text DEFAULT NULL, + UNIQUE KEY `idx_scope_id_name` (`scope_id`, `name`), + CONSTRAINT `fk_scope_id_config_scope` FOREIGN KEY (`scope_id`) + REFERENCES `icingaweb_config_scope` (`id`) + ON DELETE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; diff --git a/etc/schema/pgsql-upgrades/2.11.0.sql b/etc/schema/pgsql-upgrades/2.11.0.sql new file mode 100644 index 000000000..939010ab0 --- /dev/null +++ b/etc/schema/pgsql-upgrades/2.11.0.sql @@ -0,0 +1,48 @@ +CREATE EXTENSION IF NOT EXISTS citext; +CREATE DOMAIN bytea20 AS bytea CONSTRAINT exactly_20_bytes_long CHECK ( VALUE IS NULL OR octet_length(VALUE) = 20 ); + +CREATE TABLE "icingaweb_config_scope" ( + "id" serial, + "module" character varying(254) NOT NULL DEFAULT 'default', + "type" character varying(64) NOT NULL, + "name" citext NOT NULL, + "hash" bytea20 NOT NULL +); + +COMMENT ON COLUMN icingaweb_config_scope.hash IS 'sha1(all option tuples)'; + +ALTER TABLE ONLY "icingaweb_config_scope" + ADD CONSTRAINT pk_icingaweb_config_scope + PRIMARY KEY ( + "id" +); + +CREATE UNIQUE INDEX idx_module_type_name + ON "icingaweb_config_scope" + USING btree ( + lower((module)::text), + lower((type)::text), + lower((name)::text) +); + +CREATE TABLE "icingaweb_config_option" ( + "scope_id" int NOT NULL, + "name" character varying(254) NOT NULL, + "value" text DEFAULT NULL +); + +CREATE UNIQUE INDEX idx_scope_id_name + ON "icingaweb_config_option" + USING btree ( + scope_id, + lower((name)::text) +); + +ALTER TABLE ONLY "icingaweb_config_option" + ADD CONSTRAINT fk_scope_id_config_scope + FOREIGN KEY ( + "scope_id" + ) + REFERENCES "icingaweb_config_scope" ( + "id" +) ON DELETE CASCADE; diff --git a/etc/schema/pgsql.schema.sql b/etc/schema/pgsql.schema.sql index c1b9fa2ef..5533f6d41 100644 --- a/etc/schema/pgsql.schema.sql +++ b/etc/schema/pgsql.schema.sql @@ -4,6 +4,9 @@ CREATE OR REPLACE FUNCTION unix_timestamp(timestamp with time zone) RETURNS bigi SELECT EXTRACT(EPOCH FROM $1)::bigint AS result ' LANGUAGE sql; +CREATE EXTENSION IF NOT EXISTS citext; +CREATE DOMAIN bytea20 AS bytea CONSTRAINT exactly_20_bytes_long CHECK ( VALUE IS NULL OR octet_length(VALUE) = 20 ); + CREATE TABLE "icingaweb_group" ( "id" serial, "name" character varying(64) NOT NULL, @@ -117,3 +120,49 @@ ALTER TABLE ONLY "icingaweb_rememberme" PRIMARY KEY ( "id" ); + +CREATE TABLE "icingaweb_config_scope" ( + "id" serial, + "module" character varying(254) NOT NULL DEFAULT 'default', + "type" character varying(64) NOT NULL, + "name" citext NOT NULL, + "hash" bytea20 NOT NULL +); + +COMMENT ON COLUMN icingaweb_config_scope.hash IS 'sha1(all option tuples)'; + +ALTER TABLE ONLY "icingaweb_config_scope" + ADD CONSTRAINT pk_icingaweb_config_scope + PRIMARY KEY ( + "id" +); + +CREATE UNIQUE INDEX idx_module_type_name + ON "icingaweb_config_scope" + USING btree ( + lower((module)::text), + lower((type)::text), + lower((name)::text) +); + +CREATE TABLE "icingaweb_config_option" ( + "scope_id" int NOT NULL, + "name" character varying(254) NOT NULL, + "value" text DEFAULT NULL +); + +CREATE UNIQUE INDEX idx_scope_id_name + ON "icingaweb_config_option" + USING btree ( + scope_id, + lower((name)::text) +); + +ALTER TABLE ONLY "icingaweb_config_option" + ADD CONSTRAINT fk_scope_id_config_scope + FOREIGN KEY ( + "scope_id" + ) + REFERENCES "icingaweb_config_scope" ( + "id" +) ON DELETE CASCADE; diff --git a/library/Icinga/Model/ConfigOption.php b/library/Icinga/Model/ConfigOption.php new file mode 100644 index 000000000..15cabc35b --- /dev/null +++ b/library/Icinga/Model/ConfigOption.php @@ -0,0 +1,56 @@ + t('Config Option Name'), + 'value' => t('Config Option Value') + ]; + } + + public function getSearchColumns() + { + return ['name']; + } + + public function getDefaultSort() + { + return 'icingaweb_config_option.name'; + } + + public function createRelations(Relations $relations) + { + $relations->belongsTo('scope', ConfigScope::class) + ->setCandidateKey('scope_id'); + } +} diff --git a/library/Icinga/Model/ConfigScope.php b/library/Icinga/Model/ConfigScope.php new file mode 100644 index 000000000..e90d35a3a --- /dev/null +++ b/library/Icinga/Model/ConfigScope.php @@ -0,0 +1,102 @@ + t('Config Scope Module'), + 'type' => t('Config Scope Type'), + 'name' => t('Config Scope Name'), + 'hash' => t('Config Scope Hash') + ]; + } + + public function getSearchColumns() + { + return ['name']; + } + + public function getDefaultSort() + { + return 'icingaweb_config_scope.name'; + } + + public function createBehaviors(Behaviors $behaviors) + { + $binary = new class (['hash']) extends PropertyBehavior implements QueryAwareBehavior { + public function setQuery(Query $query) + { + if (! $query->getDb()->getAdapter() instanceof Pgsql) { + $this->properties = []; + } + } + + public function fromDb($value, $key, $context) + { + if ($value !== null) { + if (! is_resource($value)) { + throw new \UnexpectedValueException( + sprintf('%s should be a resource got %s instead', $key, get_php_type($value)) + ); + } + + return stream_get_contents($value); + } + + return null; + } + + public function toDb($value, $key, $context) + { + if (is_resource($value)) { + throw new \UnexpectedValueException(sprintf('Unexpected resource for %s', $key)); + } + + return sprintf('\\x%s', bin2hex($value)); + } + }; + + $behaviors->add($binary); + } + + public function createRelations(Relations $relations) + { + $relations->hasMany('option', ConfigOption::class) + ->setForeignKey('scope_id') + ->setJoinType('LEFT'); + } +}