diff --git a/lib/puppet/provider/keycloak_api.rb b/lib/puppet/provider/keycloak_api.rb index 50a3a6c..8b2b839 100644 --- a/lib/puppet/provider/keycloak_api.rb +++ b/lib/puppet/provider/keycloak_api.rb @@ -1,158 +1,165 @@ require 'puppet' require 'json' # Shared provider class class Puppet::Provider::KeycloakAPI < Puppet::Provider initvars # Unused but defined anyways commands kcadm_wrapper: '/opt/keycloak/bin/kcadm-wrapper.sh' @install_dir = nil @server = nil @realm = nil @user = nil @password = nil @use_wrapper = true class << self attr_accessor :install_dir attr_accessor :server attr_accessor :realm attr_accessor :user attr_accessor :password attr_accessor :use_wrapper end def self.type_properties resource_type.validproperties.reject { |p| p.to_sym == :ensure } end def type_properties self.class.type_properties end def self.camelize(value) str = value.to_s.split('_').map(&:capitalize).join str[0].downcase + str[1..-1] end def camelize(*args) self.class.camelize(*args) end def convert_property_value(value) case value when :true true when :false false else value end end def self.kcadm(action, resource, realm = nil, file = nil, fields = nil, print_id = false) kcadm_wrapper = '/opt/keycloak/bin/kcadm-wrapper.sh' arguments = [action, resource] if ['create', 'update'].include?(action) && !print_id arguments << '-o' end if realm arguments << '-r' arguments << realm end if file arguments << '-f' arguments << file end if fields arguments << '--fields' arguments << fields.join(',') end if action == 'create' && print_id arguments << '--id' end if use_wrapper == false || use_wrapper == :false auth_arguments = [ '--no-config', '--server', server, '--realm', self.realm, '--user', user, '--password', password ] cmd = [File.join(install_dir, 'bin/kcadm.sh')] + arguments + auth_arguments else cmd = [kcadm_wrapper] + arguments end execute(cmd, combine: false, failonfail: true) end def kcadm(*args) self.class.kcadm(*args) end def self.realms output = kcadm('get', 'realms', nil, nil, ['realm']) data = JSON.parse(output) realms = data.map { |r| r['realm'] } realms end def realms self.class.realms end def self.name_uuid(name) # Code lovingly taken from # https://github.com/puppetlabs/marionette-collective/blob/master/lib/mcollective/ssl.rb # This is the UUID version 5 type DNS name space which is as follows: # # 6ba7b810-9dad-11d1-80b4-00c04fd430c8 # uuid_name_space_dns = [0x6b, 0xa7, 0xb8, 0x10, 0x9d, 0xad, 0x11, 0xd1, 0x80, 0xb4, 0x00, 0xc0, 0x4f, 0xd4, 0x30, 0xc8].map { |b| b.chr }.join sha1 = Digest::SHA1.new sha1.update(uuid_name_space_dns) sha1.update(name) # first 16 bytes.. bytes = sha1.digest[0, 16].bytes.to_a # version 5 adjustments bytes[6] &= 0x0f bytes[6] |= 0x50 # variant is DCE 1.1 bytes[8] &= 0x3f bytes[8] |= 0x80 bytes = [4, 2, 2, 2, 6].map do |i| bytes.slice!(0, i).pack('C*').unpack('H*') end bytes.join('-') end def name_uuid(*args) self.class.name_uuid(*args) end + + def check_theme_exists(theme, res) + install_dir = self.class.install_dir || '/opt/keycloak' + path = File.join(install_dir, 'themes', theme) + return if File.exist?(path) + Puppet.warning("#{res}: Theme #{theme} not found at path #{path}.") + end end diff --git a/lib/puppet/provider/keycloak_client/kcadm.rb b/lib/puppet/provider/keycloak_client/kcadm.rb index eaedcf0..fc67c08 100644 --- a/lib/puppet/provider/keycloak_client/kcadm.rb +++ b/lib/puppet/provider/keycloak_client/kcadm.rb @@ -1,316 +1,322 @@ require File.expand_path(File.join(File.dirname(__FILE__), '..', 'keycloak_api')) Puppet::Type.type(:keycloak_client).provide(:kcadm, parent: Puppet::Provider::KeycloakAPI) do desc '' mk_resource_methods def attributes_properties [ :login_theme, :access_token_lifespan, ] end def dot_attributes_properties [ :access_token_lifespan, ] end def attribute_key(property) if dot_attributes_properties.include?(property) property.to_s.tr('_', '.') else property end end def self.instances clients = [] realms.each do |realm| output = kcadm('get', 'clients', realm) Puppet.debug("#{realm} clients: #{output}") begin data = JSON.parse(output) rescue JSON::ParserError Puppet.debug('Unable to parse output from kcadm get clients') data = [] end data.each do |d| # avoid built-in clients if d.key?('clientAuthenticatorType') && d['clientAuthenticatorType'] == 'client-secret' && !d.key?('name') begin secret_output = kcadm('get', "clients/#{d['id']}/client-secret", realm) rescue Puppet.debug("Unable to get clients/#{d['id']}/client-secret") secret_output = '{}' end secret_data = JSON.parse(secret_output) secret = secret_data['value'] else secret = nil end client = {} client[:ensure] = :present client[:id] = d['id'] client[:client_id] = d['clientId'] client[:realm] = realm client[:name] = "#{client[:client_id]} on #{client[:realm]}" type_properties.each do |property| camel_key = camelize(property) dot_key = property.to_s.tr('_', '.') key = property.to_s attributes = d['attributes'] || {} if property == :secret value = secret elsif d.key?(camel_key) value = d[camel_key] elsif attributes.key?(dot_key) value = attributes[dot_key] elsif attributes.key?(key) value = attributes[key] end if !!value == value # rubocop:disable Style/DoubleNegation value = value.to_s.to_sym end client[property.to_sym] = value end # The absence of a value should be 'absent' client[:login_theme] = 'absent' if client[:login_theme].nil? clients << new(client) end end clients end def self.prefetch(resources) clients = instances resources.keys.each do |name| provider = clients.find { |c| c.client_id == resources[name][:client_id] && c.realm == resources[name][:realm] } if provider resources[name].provider = provider end end end def scope_map return @scope_map if @scope_map output = kcadm('get', 'client-scopes', resource[:realm], nil, ['id', 'name']) begin data = JSON.parse(output) rescue JSON::ParserError Puppet.debug('Unable to parse output from kcadm get client-scopes') return {} end @scope_map = {} data.each do |d| @scope_map[d['name']] = d['id'] end @scope_map end def create raise(Puppet::Error, "Realm is mandatory for #{resource.type} #{resource.name}") if resource[:realm].nil? data = {} data[:id] = resource[:id] data[:clientId] = resource[:client_id] data[:secret] = resource[:secret] if resource[:secret] type_properties.each do |property| next if [:default_client_scopes, :optional_client_scopes].include?(property) next unless resource[property.to_sym] value = convert_property_value(resource[property.to_sym]) next if value == 'absent' || value == :absent || value.nil? if attributes_properties.include?(property) unless data.key?(:attributes) data[:attributes] = {} end data[:attributes][attribute_key(property)] = value else data[camelize(property)] = value end end t = Tempfile.new('keycloak_client') t.write(JSON.pretty_generate(data)) t.close Puppet.debug(IO.read(t.path)) begin + if resource[:login_theme] + check_theme_exists(resource[:login_theme], "Keycloak_client[#{resource[:name]}]") + end output = kcadm('create', 'clients', resource[:realm], t.path) Puppet.debug("create client output: #{output}") rescue Puppet::ExecutionFailure => e raise Puppet::Error, "kcadm create client failed\nError message: #{e.message}" end if resource[:default_client_scopes] || resource[:optional_client_scopes] client = JSON.parse(output) scope_id = nil end if resource[:default_client_scopes] remove_default_scopes = client['defaultClientScopes'] - resource[:default_client_scopes] begin remove_default_scopes.each do |s| scope_id = scope_map[s] kcadm('delete', "clients/#{resource[:id]}/default-client-scopes/#{scope_id}", resource[:realm]) end rescue Puppet::ExecutionFailure => e raise Puppet::Error, "kcadm delete clients/#{resource[:id]}/default-client-scopes/#{scope_id}: #{e.message}" end end if resource[:optional_client_scopes] remove_optional_scopes = client['optionalClientScopes'] - resource[:optional_client_scopes] begin remove_optional_scopes.each do |s| scope_id = scope_map[s] kcadm('delete', "clients/#{resource[:id]}/optional-client-scopes/#{scope_id}", resource[:realm]) end rescue Puppet::ExecutionFailure => e raise Puppet::Error, "kcadm delete clients/#{resource[:id]}/optional-client-scopes/#{scope_id}: #{e.message}" end end if resource[:default_client_scopes] add_default_scopes = resource[:default_client_scopes] - client['defaultClientScopes'] begin add_default_scopes.each do |s| scope_id = scope_map[s] kcadm('update', "clients/#{resource[:id]}/default-client-scopes/#{scope_id}", resource[:realm]) end rescue Puppet::ExecutionFailure => e raise Puppet::Error, "kcadm update clients/#{resource[:id]}/default-client-scopes/#{scope_id}: #{e.message}" end end if resource[:optional_client_scopes] add_optional_scopes = resource[:optional_client_scopes] - client['optionalClientScopes'] begin add_optional_scopes.each do |s| scope_id = scope_map[s] kcadm('update', "clients/#{resource[:id]}/optional-client-scopes/#{scope_id}", resource[:realm]) end rescue Puppet::ExecutionFailure => e raise Puppet::Error, "kcadm update clients/#{resource[:id]}/optional-client-scopes/#{scope_id}: #{e.message}" end end @property_hash[:ensure] = :present end def destroy raise(Puppet::Error, "Realm is mandatory for #{resource.type} #{resource.name}") if resource[:realm].nil? begin kcadm('delete', "clients/#{id}", resource[:realm]) rescue Puppet::ExecutionFailure => e raise Puppet::Error, "kcadm delete realm failed\nError message: #{e.message}" end @property_hash.clear end def exists? @property_hash[:ensure] == :present end def initialize(value = {}) super(value) @property_flush = {} end type_properties.each do |prop| define_method "#{prop}=".to_sym do |value| @property_flush[prop] = value end end def flush unless @property_flush.empty? raise(Puppet::Error, "Realm is mandatory for #{resource.type} #{resource.name}") if resource[:realm].nil? data = {} data[:clientId] = resource[:client_id] type_properties.each do |property| next if [:default_client_scopes, :optional_client_scopes].include?(property) next unless @property_flush[property.to_sym] value = convert_property_value(@property_flush[property.to_sym]) value = nil if value.to_s == 'absent' if attributes_properties.include?(property) unless data.key?(:attributes) data[:attributes] = {} end data[:attributes][attribute_key(property)] = value else data[camelize(property)] = value end end # Keycload API requires "serviceAccountsEnabled": true to be present in # the JSON when "authorizationServicesEnabled": true if data['authorizationServicesEnabled'] && data['serviceAccountsEnabled'].nil? data[:serviceAccountsEnabled] = true end # Only update if more than clientId set if data.keys.size > 1 t = Tempfile.new('keycloak_client') t.write(JSON.pretty_generate(data)) t.close Puppet.debug(IO.read(t.path)) begin + if @property_flush[:login_theme] + check_theme_exists(@property_flush[:login_theme], "Keycloak_client[#{resource[:name]}]") + end kcadm('update', "clients/#{id}", resource[:realm], t.path) rescue Puppet::ExecutionFailure => e raise Puppet::Error, "kcadm update client failed\nError message: #{e.message}" end end if @property_flush[:default_client_scopes] || @property_flush[:optional_client_scopes] scope_id = nil end if @property_flush[:default_client_scopes] remove_default_scopes = @property_hash[:default_client_scopes] - @property_flush[:default_client_scopes] begin remove_default_scopes.each do |s| scope_id = scope_map[s] kcadm('delete', "clients/#{id}/default-client-scopes/#{scope_id}", resource[:realm]) end rescue Puppet::ExecutionFailure => e raise Puppet::Error, "kcadm delete clients/#{id}/default-client-scopes/#{scope_id}: #{e.message}" end end if @property_flush[:optional_client_scopes] remove_optional_scopes = @property_hash[:optional_client_scopes] - @property_flush[:optional_client_scopes] begin remove_optional_scopes.each do |s| scope_id = scope_map[s] kcadm('delete', "clients/#{id}/optional-client-scopes/#{scope_id}", resource[:realm]) end rescue Puppet::ExecutionFailure => e raise Puppet::Error, "kcadm delete clients/#{id}/optional-client-scopes/#{scope_id}: #{e.message}" end end if @property_flush[:default_client_scopes] add_default_scopes = @property_flush[:default_client_scopes] - @property_hash[:default_client_scopes] begin add_default_scopes.each do |s| scope_id = scope_map[s] kcadm('update', "clients/#{id}/default-client-scopes/#{scope_id}", resource[:realm]) end rescue Puppet::ExecutionFailure => e raise Puppet::Error, "kcadm update clients/#{id}/default-client-scopes/#{scope_id}: #{e.message}" end end if @property_flush[:optional_client_scopes] add_optional_scopes = @property_flush[:optional_client_scopes] - @property_hash[:optional_client_scopes] begin add_optional_scopes.each do |s| scope_id = scope_map[s] kcadm('update', "clients/#{id}/optional-client-scopes/#{scope_id}", resource[:realm]) end rescue Puppet::ExecutionFailure => e raise Puppet::Error, "kcadm update clients/#{id}/optional-client-scopes/#{scope_id}: #{e.message}" end end end # Collect the resources again once they've been changed (that way `puppet # resource` will show the correct values after changes have been made). @property_hash = resource.to_hash end end diff --git a/lib/puppet/provider/keycloak_realm/kcadm.rb b/lib/puppet/provider/keycloak_realm/kcadm.rb index 6a7a5c6..2d5f5d6 100644 --- a/lib/puppet/provider/keycloak_realm/kcadm.rb +++ b/lib/puppet/provider/keycloak_realm/kcadm.rb @@ -1,364 +1,384 @@ require File.expand_path(File.join(File.dirname(__FILE__), '..', 'keycloak_api')) Puppet::Type.type(:keycloak_realm).provide(:kcadm, parent: Puppet::Provider::KeycloakAPI) do desc '' mk_resource_methods def flow_properties [ :browser_flow, :registration_flow, :direct_grant_flow, :reset_credentials_flow, :client_authentication_flow, :docker_authentication_flow, ] end def self.smtp_server_properties [ :smtp_server_user, :smtp_server_password, :smtp_server_host, :smtp_server_port, :smtp_server_auth, :smtp_server_starttls, :smtp_server_ssl, :smtp_server_envelope_from, :smtp_server_from, :smtp_server_from_display_name, :smtp_server_reply_to, :smtp_server_reply_to_display_name, ] end def self.browser_security_headers [ :content_security_policy, ] end def self.get_client_scopes(realm, type) output = kcadm('get', "realms/#{realm}/default-#{type}-client-scopes") Puppet.debug("Realms #{realm} #{type} client scopes: #{output}") data = JSON.parse(output) scopes = {} data.each do |d| scopes[d['name']] = d['id'] end Puppet.debug("Returned scopes: #{scopes}") scopes end def get_client_scopes(*args) self.class.get_client_scopes(*args) end def self.get_events_config(realm) output = kcadm('get', 'events/config', realm) Puppet.debug("#{realm} events/config: #{output}") begin data = JSON.parse(output) rescue JSON::ParserError Puppet.debug('Unable to parse output from kcadm get events/config') data = {} end data.delete('enabledEventTypes') data end def available_flows(realm) output = kcadm('get', 'authentication/flows', realm, nil, ['alias']) Puppet.debug("#{realm} authentication/flows: #{output}") begin data = JSON.parse(output) rescue JSON::ParserError Puppet.debug('Unable to parse output from kcadm get authentication/flows') return [] end data.map { |f| f['alias'] } end def self.instances realms = [] output = kcadm('get', 'realms') Puppet.debug("Realms: #{output}") begin data = JSON.parse(output) rescue JSON::ParserError Puppet.debug('Unable to parse output from kcadm get realms') data = [] end data.each do |d| realm = {} realm[:ensure] = :present realm[:id] = d['id'] realm[:name] = d['realm'] events_config = get_events_config(d['realm']) type_properties.each do |property| next if [:default_client_scopes, :optional_client_scopes].include?(property) value = if property.to_s =~ %r{events} events_config[camelize(property)] elsif browser_security_headers.include?(property) d['browserSecurityHeaders'][camelize(property)] elsif smtp_server_properties.include?(property) d['smtpServer'][camelize(property.to_s.gsub(%r{smtp_server_}, ''))] else d[camelize(property)] end if !!value == value # rubocop:disable Style/DoubleNegation value = value.to_s.to_sym end realm[property.to_sym] = value end default_scopes = get_client_scopes(realm[:name], 'default') realm[:default_client_scopes] = default_scopes.keys.map { |k| k.to_s } optional_scopes = get_client_scopes(realm[:name], 'optional') realm[:optional_client_scopes] = optional_scopes.keys.map { |k| k.to_s } realms << new(realm) end realms end def self.prefetch(resources) realms = instances resources.keys.each do |name| provider = realms.find { |realm| realm.name == name } if provider resources[name].provider = provider end end end def create data = {} events_config = {} data[:id] = resource[:id] data[:realm] = resource[:name] type_properties.each do |property| next if flow_properties.include?(property) next if [:default_client_scopes, :optional_client_scopes].include?(property) if self.class.browser_security_headers.include?(property) && !data.key?('browserSecurityHeaders') data['browserSecurityHeaders'] = {} end if self.class.smtp_server_properties.include?(property) && !data.key?('smtpServer') data['smtpServer'] = {} end if property.to_s =~ %r{events} events_config[camelize(property)] = convert_property_value(resource[property.to_sym]) elsif resource[property.to_sym] if self.class.browser_security_headers.include?(property) data['browserSecurityHeaders'][camelize(property)] = convert_property_value(resource[property.to_sym]) elsif self.class.smtp_server_properties.include?(property) && resource[property] data['smtpServer'][camelize(property.to_s.gsub(%r{smtp_server_}, ''))] = resource[property] else data[camelize(property)] = convert_property_value(resource[property.to_sym]) end end end t = Tempfile.new('keycloak_realm') t.write(JSON.pretty_generate(data)) t.close Puppet.debug(IO.read(t.path)) begin + [ + :login_theme, + :account_theme, + :admin_theme, + :email_theme, + ].each do |theme| + if resource[theme] + check_theme_exists(resource[theme], "Keycloak_realm[#{resource[:name]}]") + end + end kcadm('create', 'realms', nil, t.path) rescue Puppet::ExecutionFailure => e raise Puppet::Error, "kcadm create realm failed\nError message: #{e.message}" end scope_id = nil if resource[:default_client_scopes] default_scopes = default_scopes ||= get_client_scopes(resource[:name], 'default') remove_default_scopes = default_scopes.keys - resource[:default_client_scopes] begin remove_default_scopes.each do |s| scope_id = default_scopes[s] kcadm('delete', "realms/#{resource[:name]}/default-default-client-scopes/#{scope_id}") end rescue Puppet::ExecutionFailure => e raise Puppet::Error, "kcadm delete realms/#{resource[:name]}/default-default-client-scopes/#{scope_id}: #{e.message}" end end if resource[:optional_client_scopes] optional_scopes = optional_scopes ||= get_client_scopes(resource[:name], 'optional') remove_optional_scopes = optional_scopes.keys - resource[:optional_client_scopes] begin remove_optional_scopes.each do |s| scope_id = optional_scopes[s] kcadm('delete', "realms/#{resource[:name]}/default-optional-client-scopes/#{scope_id}") end rescue Puppet::ExecutionFailure => e raise Puppet::Error, "kcadm delete realms/#{resource[:name]}/default-optional-client-scopes/#{scope_id}: #{e.message}" end end if resource[:default_client_scopes] default_scopes = default_scopes ||= get_client_scopes(resource[:name], 'default') add_default_scopes = resource[:default_client_scopes] - default_scopes.keys begin add_default_scopes.each do |s| scope_id = default_scopes[s] kcadm('update', "realms/#{resource[:name]}/default-default-client-scopes/#{scope_id}") end rescue Puppet::ExecutionFailure => e raise Puppet::Error, "kcadm update realms/#{resource[:name]}/default-default-client-scopes/#{scope_id}: #{e.message}" end end if resource[:optional_client_scopes] optional_scopes = optional_scopes ||= get_client_scopes(resource[:name], 'optional') add_optional_scopes = resource[:optional_client_scopes] - optional_scopes.keys begin add_optional_scopes.each do |s| scope_id = optional_scopes[s] kcadm('update', "realms/#{resource[:name]}/default-optional-client-scopes/#{scope_id}") end rescue Puppet::ExecutionFailure => e raise Puppet::Error, "kcadm update realms/#{resource[:name]}/default-optional-client-scopes/#{scope_id}: #{e.message}" end end unless events_config.empty? events_config_t = Tempfile.new('keycloak_events_config') events_config_t.write(JSON.pretty_generate(events_config)) events_config_t.close Puppet.debug(IO.read(events_config_t.path)) begin kcadm('update', 'events/config', resource[:name], events_config_t.path) rescue Puppet::ExecutionFailure => e raise Puppet::Error, "kcadm update events config failed\nError message: #{e.message}" end end @property_hash[:ensure] = :present end def destroy begin kcadm('delete', "realms/#{resource[:name]}") rescue Puppet::ExecutionFailure => e raise Puppet::Error, "kcadm delete realm failed\nError message: #{e.message}" end @property_hash.clear end def exists? @property_hash[:ensure] == :present end def initialize(value = {}) super(value) @property_flush = {} end type_properties.each do |prop| define_method "#{prop}=".to_sym do |value| @property_flush[prop] = value end end def flush unless @property_flush.empty? data = {} events_config = {} type_properties.each do |property| next if [:default_client_scopes, :optional_client_scopes].include?(property) if flow_properties.include?(property) && !available_flows(resource[:name]).include?(resource[property.to_sym]) Puppet.warning("Keycloak_realm[#{resource[:name]}]: #{property} '#{resource[property.to_sym]}' does not exist, skipping") next end if self.class.browser_security_headers.include?(property) && !data.key?('browserSecurityHeaders') data['browserSecurityHeaders'] = {} end if self.class.smtp_server_properties.include?(property) && !data.key?('smtpServer') data['smtpServer'] = {} end if @property_flush[property.to_sym] || resource[property.to_sym] if self.class.browser_security_headers.include?(property) data['browserSecurityHeaders'][camelize(property)] = convert_property_value(resource[property.to_sym]) elsif self.class.smtp_server_properties.include?(property) && resource[property] data['smtpServer'][camelize(property.to_s.gsub(%r{smtp_server_}, ''))] = resource[property] else data[camelize(property)] = convert_property_value(resource[property.to_sym]) end end if property.to_s =~ %r{events} events_config[camelize(property)] = convert_property_value(resource[property.to_sym]) end end unless data.empty? t = Tempfile.new('keycloak_realm') t.write(JSON.pretty_generate(data)) t.close Puppet.debug(IO.read(t.path)) begin + [ + :login_theme, + :account_theme, + :admin_theme, + :email_theme, + ].each do |theme| + if @property_flush[theme] + check_theme_exists(@property_flush[theme], "Keycloak_realm[#{resource[:name]}]") + end + end kcadm('update', "realms/#{resource[:name]}", nil, t.path) rescue Puppet::ExecutionFailure => e raise Puppet::Error, "kcadm update realm failed\nError message: #{e.message}" end end scope_id = nil if @property_flush[:default_client_scopes] default_scopes = default_scopes ||= get_client_scopes(resource[:name], 'default') remove_default_scopes = default_scopes.keys - @property_flush[:default_client_scopes] begin remove_default_scopes.each do |s| scope_id = default_scopes[s] kcadm('delete', "realms/#{resource[:name]}/default-default-client-scopes/#{scope_id}") end rescue Puppet::ExecutionFailure => e raise Puppet::Error, "kcadm delete realms/#{resource[:name]}/default-default-client-scopes/#{scope_id}: #{e.message}" end end if @property_flush[:optional_client_scopes] optional_scopes = optional_scopes ||= get_client_scopes(resource[:name], 'optional') remove_optional_scopes = optional_scopes.keys - @property_flush[:optional_client_scopes] begin remove_optional_scopes.each do |s| scope_id = optional_scopes[s] kcadm('delete', "realms/#{resource[:name]}/default-optional-client-scopes/#{scope_id}") end rescue Puppet::ExecutionFailure => e raise Puppet::Error, "kcadm delete realms/#{resource[:name]}/default-optional-client-scopes/#{scope_id}: #{e.message}" end end if @property_flush[:default_client_scopes] default_scopes = default_scopes ||= get_client_scopes(resource[:name], 'default') add_default_scopes = @property_flush[:default_client_scopes] - default_scopes.keys begin add_default_scopes.each do |s| scope_id = default_scopes[s] kcadm('update', "realms/#{resource[:name]}/default-default-client-scopes/#{scope_id}") end rescue Puppet::ExecutionFailure => e raise Puppet::Error, "kcadm update realms/#{resource[:name]}/default-default-client-scopes/#{scope_id}: #{e.message}" end end if @property_flush[:optional_client_scopes] optional_scopes = optional_scopes ||= get_client_scopes(resource[:name], 'optional') add_optional_scopes = @property_flush[:optional_client_scopes] - optional_scopes.keys begin add_optional_scopes.each do |s| scope_id = optional_scopes[s] kcadm('update', "realms/#{resource[:name]}/default-optional-client-scopes/#{scope_id}") end rescue Puppet::ExecutionFailure => e raise Puppet::Error, "kcadm update realms/#{resource[:name]}/default-optional-client-scopes/#{scope_id}: #{e.message}" end end unless events_config.empty? events_config_t = Tempfile.new('keycloak_events_config') events_config_t.write(JSON.pretty_generate(events_config)) events_config_t.close Puppet.debug(IO.read(events_config_t.path)) begin kcadm('update', 'events/config', resource[:name], events_config_t.path) rescue Puppet::ExecutionFailure => e raise Puppet::Error, "kcadm update events config failed\nError message: #{e.message}" end end end # Collect the resources again once they've been changed (that way `puppet # resource` will show the correct values after changes have been made). @property_hash = resource.to_hash end end