foreman_chef 0.0.4 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +11 -5
- data/app/helpers/foreman_chef/chef_proxy_form.rb +14 -0
- data/app/lib/actions/foreman_chef/client/destroy.rb +33 -0
- data/app/lib/actions/foreman_chef/host/destroy.rb +43 -0
- data/app/lib/proxy_api/foreman_chef/chef_proxy.rb +53 -0
- data/app/models/foreman_chef/chef_proxy_association.rb +10 -0
- data/app/models/foreman_chef/concerns/host_action_subject.rb +19 -0
- data/app/models/foreman_chef/fact_importer.rb +4 -0
- data/app/models/foreman_chef/host_extensions.rb +16 -0
- data/app/models/foreman_chef/hostgroup_extensions.rb +18 -0
- data/app/models/foreman_chef/smart_proxy_extensions.rb +38 -0
- data/app/models/setting/foreman_chef.rb +17 -0
- data/app/overrides/add_chef_proxy.rb +16 -0
- data/db/migrate/20140220145827_add_chef_proxy_id_to_host.rb +5 -0
- data/db/migrate/20140513144804_add_chef_proxy_id_to_hostgroup.rb +5 -0
- data/db/seeds.rb +1 -0
- data/lib/foreman_chef/engine.rb +32 -6
- data/lib/foreman_chef/version.rb +1 -1
- metadata +44 -4
- data/test/dummy/log/development.log +0 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 48b50dcd6355797ece89dee99d3f9ebef0d347e4
|
4
|
+
data.tar.gz: 79ae27dc6b1c7d23b6d7d2acc942b3f6a94aea2a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 894eca488c8631c39043510f530404c0f3cc1ea6bee1854cd41b7d0a214ac5d04fc08754390727b23cc01aa27b539625a45be881946a12b969b431ad953ad1f7
|
7
|
+
data.tar.gz: 3d2920bec28fe08a6c7bbb39ee6e61a779c35d31cdf4c0f9e740515fb940a674ae055491b65aeb5441b6af8ead9990b969f69c0213b29b05b3cfaee02d548aec
|
data/README.md
CHANGED
@@ -1,17 +1,17 @@
|
|
1
1
|
# ForemanChef
|
2
2
|
|
3
|
-
This plugin adds a Chef fact
|
3
|
+
This plugin adds a Chef fact importer to Foreman. It basically means that when you setup your chef
|
4
4
|
clients to use foreman handlers (https://github.com/theforeman/chef-handler-foreman) and install
|
5
5
|
this plugin you receive nested facts from chef-client.
|
6
6
|
|
7
7
|
This plugin expects foreman to support nested facts which is was added in 1.4.
|
8
|
-
To install this plugin you just have to add this line to your
|
8
|
+
To install this plugin you just have to add this line to your Gemfile.
|
9
9
|
|
10
10
|
```ruby
|
11
|
-
gem 'foreman_chef'
|
11
|
+
gem 'foreman_chef'
|
12
12
|
```
|
13
13
|
|
14
|
-
and run bundle install
|
14
|
+
and run ```bundle install```. Don't forget to restart foreman after this change.
|
15
15
|
|
16
16
|
If you want to use this in production I recommend to combine this plugin with foreman-background
|
17
17
|
(https://github.com/ohadlevy/foreman-background) which runs report and facts import as a background
|
@@ -19,4 +19,10 @@ tasks.
|
|
19
19
|
|
20
20
|
## License
|
21
21
|
|
22
|
-
|
22
|
+
Copyright (c) 2013-2014 Marek Hulán
|
23
|
+
|
24
|
+
This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
|
25
|
+
|
26
|
+
This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
27
|
+
|
28
|
+
You should have received a copy of the GNU General Public License along with this program. If not, see http://www.gnu.org/licenses/.
|
@@ -0,0 +1,14 @@
|
|
1
|
+
module ForemanChef
|
2
|
+
module ChefProxyForm
|
3
|
+
def chef_proxy_form(f)
|
4
|
+
# Don't show this if we have no Chef proxies, otherwise always include blank
|
5
|
+
# so the user can choose not to use puppet on this host
|
6
|
+
proxies = SmartProxy.with_taxonomy_scope_override(@location,@organization).with_features('Chef')
|
7
|
+
return if proxies.count == 0
|
8
|
+
select_f f, :chef_proxy_id, proxies, :id, :name,
|
9
|
+
{ :include_blank => blank_or_inherit_f(f, :chef_proxy) },
|
10
|
+
{ :label => _("Chef proxy"),
|
11
|
+
:help_inline => _("Use this foreman proxy as an entry point to your Chef, node will be managed via this proxy") }
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module Actions
|
2
|
+
module ForemanChef
|
3
|
+
module Client
|
4
|
+
class Destroy < Actions::EntryAction
|
5
|
+
|
6
|
+
def plan(fqdn, proxy)
|
7
|
+
if ::Setting::ForemanChef.auto_deletion
|
8
|
+
client_exists_in_chef = proxy.show_client(fqdn)
|
9
|
+
if client_exists_in_chef
|
10
|
+
plan_self :chef_proxy_id => proxy.id, :fqdn => fqdn
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def run
|
16
|
+
proxy = ::SmartProxy.find_by_id(input[:chef_proxy_id])
|
17
|
+
action_logger.debug "Deleting client #{input[:fqdn]} on proxy #{proxy.name} at #{proxy.url}"
|
18
|
+
self.output = proxy.delete_client(input[:fqdn])
|
19
|
+
end
|
20
|
+
|
21
|
+
def humanized_name
|
22
|
+
_("Delete client")
|
23
|
+
end
|
24
|
+
|
25
|
+
def humanized_input
|
26
|
+
input[:fqdn]
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
@@ -0,0 +1,43 @@
|
|
1
|
+
module Actions
|
2
|
+
module ForemanChef
|
3
|
+
module Host
|
4
|
+
class Destroy < Actions::EntryAction
|
5
|
+
|
6
|
+
def plan(host)
|
7
|
+
action_subject(host)
|
8
|
+
if (::Setting::ForemanChef.auto_deletion && proxy = ::SmartProxy.find_by_id(host.chef_proxy_id))
|
9
|
+
node_exists_in_chef = proxy.show_node(host.name)
|
10
|
+
if node_exists_in_chef
|
11
|
+
plan_self :chef_proxy_id => host.chef_proxy_id
|
12
|
+
end
|
13
|
+
|
14
|
+
plan_action Actions::ForemanChef::Client::Destroy, host.name, proxy
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def run
|
19
|
+
proxy = ::SmartProxy.find_by_id(input[:chef_proxy_id])
|
20
|
+
action_logger.debug "Deleting #{input[:host][:name]} on proxy #{proxy.name} at #{proxy.url}"
|
21
|
+
self.output = proxy.delete_node(input[:host][:name])
|
22
|
+
end
|
23
|
+
|
24
|
+
def humanized_name
|
25
|
+
_("Delete host")
|
26
|
+
end
|
27
|
+
|
28
|
+
def humanized_input
|
29
|
+
input[:host] && input[:host][:name]
|
30
|
+
end
|
31
|
+
|
32
|
+
def cli_example
|
33
|
+
return unless input[:host]
|
34
|
+
<<-EXAMPLE
|
35
|
+
hammer host delete --id '#{task_input[:host][:id]}'
|
36
|
+
EXAMPLE
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
@@ -0,0 +1,53 @@
|
|
1
|
+
module ProxyAPI
|
2
|
+
module ForemanChef
|
3
|
+
class ChefProxy
|
4
|
+
PREFIX = 'chef'
|
5
|
+
|
6
|
+
class Node < ProxyAPI::Resource
|
7
|
+
def initialize(args)
|
8
|
+
@url = "#{args[:url]}/#{PREFIX}/nodes"
|
9
|
+
super args
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
class Client < ProxyAPI::Resource
|
14
|
+
def initialize(args)
|
15
|
+
@url = "#{args[:url]}/#{PREFIX}/clients"
|
16
|
+
super args
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def initialize(args)
|
21
|
+
@args = args
|
22
|
+
end
|
23
|
+
|
24
|
+
# Shows a Chef Node entry
|
25
|
+
# [+key+] : String containing the hostname
|
26
|
+
# Returns : Hash representation of host on chef server
|
27
|
+
def show_node(key)
|
28
|
+
Node.new(@args).send(:get, key)
|
29
|
+
end
|
30
|
+
|
31
|
+
# Deletes a Chef Node entry
|
32
|
+
# [+key+] : String containing the hostname
|
33
|
+
# Returns : Boolean status
|
34
|
+
def delete_node(key)
|
35
|
+
Node.new(@args).send(:delete, key)
|
36
|
+
end
|
37
|
+
|
38
|
+
# Shows a Chef Client entry
|
39
|
+
# [+key+] : String containing the hostname
|
40
|
+
# Returns : Hash representation of host on chef server
|
41
|
+
def show_client(key)
|
42
|
+
Client.new(@args).send(:get, key)
|
43
|
+
end
|
44
|
+
|
45
|
+
# Deletes a Chef Client entry
|
46
|
+
# [+key+] : String containing the hostname
|
47
|
+
# Returns : Boolean status
|
48
|
+
def delete_client(key)
|
49
|
+
Client.new(@args).send(:delete, key)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module ForemanChef
|
2
|
+
module Concerns
|
3
|
+
module HostActionSubject
|
4
|
+
extend ActiveSupport::Concern
|
5
|
+
include ForemanTasks::Concerns::ActionSubject
|
6
|
+
include ForemanTasks::Concerns::ActionTriggering
|
7
|
+
|
8
|
+
def destroy_action
|
9
|
+
sync_action!
|
10
|
+
::Actions::ForemanChef::Host::Destroy
|
11
|
+
end
|
12
|
+
|
13
|
+
def action_input_key
|
14
|
+
"host"
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module ForemanChef
|
2
|
+
module HostExtensions
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
included do
|
6
|
+
alias_method_chain :set_hostgroup_defaults, :chef_proxy
|
7
|
+
end
|
8
|
+
|
9
|
+
def set_hostgroup_defaults_with_chef_proxy
|
10
|
+
set_hostgroup_defaults_without_chef_proxy
|
11
|
+
return unless hostgroup
|
12
|
+
assign_hostgroup_attributes(['chef_proxy_id'])
|
13
|
+
end
|
14
|
+
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module ForemanChef
|
2
|
+
module HostgroupExtensions
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
def inherited_chef_proxy_id
|
6
|
+
read_attribute(:chef_proxy_id) || nested(:chef_proxy_id)
|
7
|
+
end
|
8
|
+
|
9
|
+
def chef_proxy
|
10
|
+
if ancestry.present?
|
11
|
+
SmartProxy.with_features('Chef').find_by_id(inherited_chef_proxy_id)
|
12
|
+
else
|
13
|
+
super
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
module ForemanChef
|
2
|
+
module SmartProxyExtensions
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
# create or overwrite instance methods...
|
6
|
+
def show_node(fqdn)
|
7
|
+
reply = ProxyAPI::ForemanChef::ChefProxy.new(:url => url).show_node(fqdn)
|
8
|
+
JSON.parse(reply)
|
9
|
+
rescue RestClient::ResourceNotFound
|
10
|
+
logger.debug "Node '#{fqdn}' not found"
|
11
|
+
return false
|
12
|
+
end
|
13
|
+
|
14
|
+
def delete_node(fqdn)
|
15
|
+
begin
|
16
|
+
reply = ProxyAPI::ForemanChef::ChefProxy.new(:url => url).delete_node(fqdn)
|
17
|
+
JSON.parse(reply)
|
18
|
+
#rescue RestClient::
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def show_client(fqdn)
|
23
|
+
reply = ProxyAPI::ForemanChef::ChefProxy.new(:url => url).show_client(fqdn)
|
24
|
+
JSON.parse(reply)
|
25
|
+
rescue RestClient::ResourceNotFound
|
26
|
+
logger.debug "Client '#{fqdn}' not found"
|
27
|
+
return false
|
28
|
+
end
|
29
|
+
|
30
|
+
def delete_client(fqdn)
|
31
|
+
begin
|
32
|
+
reply = ProxyAPI::ForemanChef::ChefProxy.new(:url => url).delete_client(fqdn)
|
33
|
+
JSON.parse(reply)
|
34
|
+
#rescue RestClient::
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
class Setting::ForemanChef < Setting
|
2
|
+
|
3
|
+
def self.load_defaults
|
4
|
+
# Check the table exists
|
5
|
+
return unless super
|
6
|
+
|
7
|
+
self.transaction do
|
8
|
+
[
|
9
|
+
self.set('auto_deletion', N_("Enable the auto deletion of mapped objects in chef-server through foreman-proxy (currently node and client upon host deletion)"), true),
|
10
|
+
].each { |s| self.create! s.update(:category => "Setting::ForemanChef")}
|
11
|
+
end
|
12
|
+
|
13
|
+
true
|
14
|
+
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
Deface::Override.new(:virtual_path => "hosts/_form",
|
2
|
+
:name => "add_chef_proxy",
|
3
|
+
:insert_bottom => "div#primary",
|
4
|
+
:text => ("<%= chef_proxy_form(f) %>")
|
5
|
+
)
|
6
|
+
|
7
|
+
Deface::Override.new(:virtual_path => "hostgroups/_form",
|
8
|
+
:name => "add_chef_proxy",
|
9
|
+
:insert_bottom => "div#primary",
|
10
|
+
:text => ("<%= chef_proxy_form(f) %>")
|
11
|
+
)
|
12
|
+
|
13
|
+
# strings used above, for the purposes of extraction only, as they're not
|
14
|
+
# detected within the full override template string above
|
15
|
+
N_('Chef proxy')
|
16
|
+
N_('Use this foreman proxy as an entry point to your Chef, node will be managed via this proxy')
|
data/db/seeds.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
Feature.find_or_create_by_name('Chef')
|
data/lib/foreman_chef/engine.rb
CHANGED
@@ -1,25 +1,51 @@
|
|
1
|
+
require 'deface'
|
2
|
+
|
1
3
|
module ForemanChef
|
2
4
|
#Inherit from the Rails module of the parent app (Foreman), not the plugin.
|
3
5
|
#Thus, inhereits from ::Rails::Engine and not from Rails::Engine
|
4
6
|
class Engine < ::Rails::Engine
|
5
7
|
|
8
|
+
rake_tasks do
|
9
|
+
Rake::Task['db:seed'].enhance do
|
10
|
+
ForemanChef::Engine.load_seed
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
initializer 'foreman_chef.load_default_settings', :before => :load_config_initializers do
|
15
|
+
require_dependency File.expand_path('../../../app/models/setting/foreman_chef.rb', __FILE__) if (Setting.table_exists? rescue(false))
|
16
|
+
end
|
17
|
+
|
6
18
|
initializer "foreman_chef.load_app_instance_data" do |app|
|
7
19
|
app.config.paths['db/migrate'] += ForemanChef::Engine.paths['db/migrate'].existent
|
8
20
|
end
|
9
21
|
|
10
|
-
initializer
|
22
|
+
initializer "foreman_chef.register_paths" do |app|
|
23
|
+
ForemanTasks.dynflow.config.eager_load_paths.concat(%W[#{ForemanChef::Engine.root}/app/lib/actions])
|
24
|
+
end
|
25
|
+
|
26
|
+
initializer 'foreman_chef.register_plugin', :after => :finisher_hook do |app|
|
11
27
|
Foreman::Plugin.register :foreman_chef do
|
12
28
|
requires_foreman '>= 1.4'
|
13
|
-
end
|
29
|
+
end
|
14
30
|
end
|
15
31
|
|
16
|
-
|
17
|
-
|
18
|
-
|
32
|
+
initializer 'foreman_chef.chef_proxy_form' do |app|
|
33
|
+
ActionView::Base.send :include, ChefProxyForm
|
34
|
+
end
|
35
|
+
|
36
|
+
initializer 'foreman_chef.dynflow_world' do |app|
|
37
|
+
ForemanTasks.dynflow.require!
|
38
|
+
end
|
19
39
|
|
20
|
-
#Include
|
40
|
+
#Include extensions to models in this config.to_prepare block
|
21
41
|
config.to_prepare do
|
42
|
+
::Host::Managed.send :include, ForemanChef::HostExtensions
|
43
|
+
::Hostgroup.send :include, ForemanChef::HostgroupExtensions
|
44
|
+
::Host::Managed.send :include, ChefProxyAssociation
|
45
|
+
::Hostgroup.send :include, ChefProxyAssociation
|
46
|
+
::SmartProxy.send :include, SmartProxyExtensions
|
22
47
|
::FactImporter.register_fact_importer(:foreman_chef, ForemanChef::FactImporter)
|
48
|
+
::Host::Base.send :include, ForemanChef::Concerns::HostActionSubject
|
23
49
|
end
|
24
50
|
end
|
25
51
|
end
|
data/lib/foreman_chef/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: foreman_chef
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0
|
4
|
+
version: 0.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Marek Hulan
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2015-01-14 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rails
|
@@ -24,6 +24,34 @@ dependencies:
|
|
24
24
|
- - ~>
|
25
25
|
- !ruby/object:Gem::Version
|
26
26
|
version: 3.2.8
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: deface
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - '>='
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - '>='
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: foreman-tasks
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ~>
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: 0.6.9
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ~>
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: 0.6.9
|
27
55
|
- !ruby/object:Gem::Dependency
|
28
56
|
name: sqlite3
|
29
57
|
requirement: !ruby/object:Gem::Requirement
|
@@ -45,9 +73,23 @@ executables: []
|
|
45
73
|
extensions: []
|
46
74
|
extra_rdoc_files: []
|
47
75
|
files:
|
76
|
+
- app/overrides/add_chef_proxy.rb
|
77
|
+
- app/helpers/foreman_chef/chef_proxy_form.rb
|
48
78
|
- app/models/foreman_chef/fact_name.rb
|
79
|
+
- app/models/foreman_chef/smart_proxy_extensions.rb
|
80
|
+
- app/models/foreman_chef/chef_proxy_association.rb
|
81
|
+
- app/models/foreman_chef/hostgroup_extensions.rb
|
82
|
+
- app/models/foreman_chef/host_extensions.rb
|
49
83
|
- app/models/foreman_chef/fact_importer.rb
|
84
|
+
- app/models/foreman_chef/concerns/host_action_subject.rb
|
85
|
+
- app/models/setting/foreman_chef.rb
|
86
|
+
- app/lib/actions/foreman_chef/client/destroy.rb
|
87
|
+
- app/lib/actions/foreman_chef/host/destroy.rb
|
88
|
+
- app/lib/proxy_api/foreman_chef/chef_proxy.rb
|
50
89
|
- config/routes.rb
|
90
|
+
- db/migrate/20140513144804_add_chef_proxy_id_to_hostgroup.rb
|
91
|
+
- db/migrate/20140220145827_add_chef_proxy_id_to_host.rb
|
92
|
+
- db/seeds.rb
|
51
93
|
- lib/foreman_chef/engine.rb
|
52
94
|
- lib/foreman_chef/version.rb
|
53
95
|
- lib/foreman_chef.rb
|
@@ -67,7 +109,6 @@ files:
|
|
67
109
|
- test/dummy/public/404.html
|
68
110
|
- test/dummy/public/500.html
|
69
111
|
- test/dummy/public/422.html
|
70
|
-
- test/dummy/log/development.log
|
71
112
|
- test/dummy/config/database.yml
|
72
113
|
- test/dummy/config/environment.rb
|
73
114
|
- test/dummy/config/locales/en.yml
|
@@ -124,7 +165,6 @@ test_files:
|
|
124
165
|
- test/dummy/public/404.html
|
125
166
|
- test/dummy/public/500.html
|
126
167
|
- test/dummy/public/422.html
|
127
|
-
- test/dummy/log/development.log
|
128
168
|
- test/dummy/config/database.yml
|
129
169
|
- test/dummy/config/environment.rb
|
130
170
|
- test/dummy/config/locales/en.yml
|