From 749d45f20afe521383ff788efdc4ed2b73fbbbb0 Mon Sep 17 00:00:00 2001 From: Tom Hughes Date: Thu, 5 Oct 2023 13:29:56 +0000 Subject: [PATCH] Add postgresql_sequence for managing sequences --- cookbooks/postgresql/libraries/postgresql.rb | 16 ++++ cookbooks/postgresql/resources/sequence.rb | 97 ++++++++++++++++++++ 2 files changed, 113 insertions(+) create mode 100644 cookbooks/postgresql/resources/sequence.rb diff --git a/cookbooks/postgresql/libraries/postgresql.rb b/cookbooks/postgresql/libraries/postgresql.rb index 4f0d4a1fe..9a53331dd 100644 --- a/cookbooks/postgresql/libraries/postgresql.rb +++ b/cookbooks/postgresql/libraries/postgresql.rb @@ -8,6 +8,10 @@ module OpenStreetMap :select, :insert, :update, :delete, :truncate, :references, :trigger ].freeze + SEQUENCE_PRIVILEGES = [ + :usage, :select, :update + ].freeze + def initialize(cluster) @cluster = cluster end @@ -122,6 +126,18 @@ module OpenStreetMap end end + def sequences(database) + @sequences ||= {} + @sequences[database] ||= query("SELECT n.nspname, c.relname, u.usename, c.relacl FROM pg_class AS c INNER JOIN pg_user AS u ON c.relowner = u.usesysid INNER JOIN pg_namespace AS n ON c.relnamespace = n.oid WHERE c.relkind = 'S'", :database => database).each_with_object({}) do |sequence, sequences| + name = "#{sequence[:nspname]}.#{sequence[:relname]}" + + sequences[name] = { + :owner => sequence[:usename], + :permissions => parse_acl(sequence[:relacl] || "{}") + } + end + end + private def parse_acl(acl) diff --git a/cookbooks/postgresql/resources/sequence.rb b/cookbooks/postgresql/resources/sequence.rb new file mode 100644 index 000000000..dae657c09 --- /dev/null +++ b/cookbooks/postgresql/resources/sequence.rb @@ -0,0 +1,97 @@ +# +# Cookbook:: postgresql +# Resource:: postgresql_sequence +# +# Copyright:: 2023, OpenStreetMap Foundation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +unified_mode true + +default_action :create + +property :sequence, :kind_of => String, :name_property => true +property :cluster, :kind_of => String, :required => true +property :database, :kind_of => String, :required => true +property :schema, :kind_of => String, :default => "public" +property :owner, :kind_of => String, :required => [:create] +property :permissions, :kind_of => Hash, :default => {} + +action :create do + if sequences.include?(qualified_name) + if new_resource.owner != sequences[qualified_name][:owner] + converge_by("set owner for #{new_resource} to #{new_resource.owner}") do + Chef::Log.info("Setting owner for #{new_resource} to #{new_resource.owner}") + cluster.execute(:command => "ALTER SEQUENCE #{qualified_name} OWNER TO \"#{new_resource.owner}\"", :database => new_resource.database) + end + end + + sequences[qualified_name][:permissions].each_key do |user| + next if new_resource.permissions[user] + + converge_by("revoke all for #{user} on #{new_resource}") do + Chef::Log.info("Revoking all for #{user} on #{new_resource}") + cluster.execute(:command => "REVOKE ALL ON TABLE #{qualified_name} FROM \"#{user}\"", :database => new_resource.database) + end + end + + new_resource.permissions.each do |user, new_privileges| + current_privileges = sequences[qualified_name][:permissions][user] || {} + new_privileges = Array(new_privileges) + + if new_privileges.include?(:all) + new_privileges |= OpenStreetMap::PostgreSQL::SEQUENCE_PRIVILEGES + end + + OpenStreetMap::PostgreSQL::SEQUENCE_PRIVILEGES.each do |privilege| + if new_privileges.include?(privilege) + unless current_privileges.include?(privilege) + converge_by("grant #{privilege} for #{user} on #{new_resource}") do + Chef::Log.info("Granting #{privilege} for #{user} on #{new_resource}") + cluster.execute(:command => "GRANT #{privilege.to_s.upcase} ON SEQUENCE #{qualified_name} TO \"#{user}\"", :database => new_resource.database) + end + end + elsif current_privileges.include?(privilege) + converge_by("revoke #{privilege} for #{user} on #{new_resource}") do + Chef::Log.info("Revoking #{privilege} for #{user} on #{new_resource}") + cluster.execute(:command => "REVOKE #{privilege.to_s.upcase} ON SEQUENCE #{qualified_name} FROM \"#{user}\"", :database => new_resource.database) + end + end + end + end + end +end + +action :drop do + if sequences.include?(qualified_name) + converge_by("drop #{new_resource}") do + Chef::Log.info("Dropping #{new_resource}") + cluster.execute(:command => "DROP SEQUENCE #{qualified_name}", :database => new_resource.database) + end + end +end + +action_class do + def cluster + @cluster ||= OpenStreetMap::PostgreSQL.new(new_resource.cluster) + end + + def sequences + @sequences ||= cluster.sequences(new_resource.database) + end + + def qualified_name + "#{new_resource.schema}.#{new_resource.name}" + end +end -- 2.39.5