controlrepo 2.0.1 → 2.0.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Gemfile +1 -0
- data/Gemfile.lock +3 -1
- data/README.md +40 -0
- data/controlrepo.gemspec +2 -1
- data/factsets/CentOS-6.6-64.json +342 -0
- data/factsets/CentOS-7.0-64.json +352 -0
- data/factsets/Debian-7.8-64.json +338 -0
- data/factsets/Ubuntu-12.04-32.json +328 -0
- data/factsets/Ubuntu-14.04-64.json +373 -0
- data/factsets/Windows_Server-2008r2-64.json +183 -0
- data/factsets/Windows_Server-2012r2-64.json +164 -0
- data/lib/controlrepo.rb +17 -2
- data/lib/controlrepo/beaker.rb +5 -5
- data/lib/controlrepo/rake_tasks.rb +28 -27
- data/lib/controlrepo/testconfig.rb +23 -15
- data/templates/acceptance_test_spec.rb.erb +1 -1
- data/templates/controlrepo.yaml.erb +38 -0
- data/templates/nodeset.yaml.erb +9 -9
- metadata +24 -2
@@ -0,0 +1,164 @@
|
|
1
|
+
{
|
2
|
+
"name": "win-e5k8tm30719",
|
3
|
+
"values": {
|
4
|
+
"agent_specified_environment": "production",
|
5
|
+
"architecture": "x64",
|
6
|
+
"dhcp_servers": {
|
7
|
+
"Ethernet": "10.0.2.2",
|
8
|
+
"system": "10.0.2.2"
|
9
|
+
},
|
10
|
+
"dmi": {
|
11
|
+
"manufacturer": "innotek GmbH",
|
12
|
+
"product": {
|
13
|
+
"name": "VirtualBox",
|
14
|
+
"serial_number": "0"
|
15
|
+
}
|
16
|
+
},
|
17
|
+
"env_windows_installdir": "C:\\Program Files\\Puppet Labs\\Puppet",
|
18
|
+
"facterversion": "3.1.1",
|
19
|
+
"fqdn": "WIN-E5K8TM30719",
|
20
|
+
"hardwareisa": "x64",
|
21
|
+
"hardwaremodel": "x86_64",
|
22
|
+
"hostname": "WIN-E5K8TM30719",
|
23
|
+
"id": "WIN-E5K8TM30719\\vagrant",
|
24
|
+
"identity": {
|
25
|
+
"user": "WIN-E5K8TM30719\\vagrant"
|
26
|
+
},
|
27
|
+
"interfaces": "Ethernet",
|
28
|
+
"ipaddress": "10.0.2.15",
|
29
|
+
"ipaddress6": "fe80::a180:36e0:3a6e:1005%12",
|
30
|
+
"ipaddress6_Ethernet": "fe80::a180:36e0:3a6e:1005%12",
|
31
|
+
"ipaddress_Ethernet": "10.0.2.15",
|
32
|
+
"is_virtual": true,
|
33
|
+
"kernel": "windows",
|
34
|
+
"kernelmajversion": "6.3",
|
35
|
+
"kernelrelease": "6.3.9600",
|
36
|
+
"kernelversion": "6.3.9600",
|
37
|
+
"macaddress": "08:00:27:81:38:FA",
|
38
|
+
"macaddress_Ethernet": "08:00:27:81:38:FA",
|
39
|
+
"manufacturer": "innotek GmbH",
|
40
|
+
"memory": {
|
41
|
+
"system": {
|
42
|
+
"available": "1.42 GiB",
|
43
|
+
"available_bytes": 1521610752,
|
44
|
+
"capacity": "29.13%",
|
45
|
+
"total": "2.00 GiB",
|
46
|
+
"total_bytes": 2147012608,
|
47
|
+
"used": "596.43 MiB",
|
48
|
+
"used_bytes": 625401856
|
49
|
+
}
|
50
|
+
},
|
51
|
+
"memoryfree": "1.42 GiB",
|
52
|
+
"memoryfree_mb": 1451.12109375,
|
53
|
+
"memorysize": "2.00 GiB",
|
54
|
+
"memorysize_mb": 2047.55078125,
|
55
|
+
"mtu_Ethernet": 1500,
|
56
|
+
"netmask": "255.255.255.0",
|
57
|
+
"netmask6": "ffff:ffff:ffff:ffff::",
|
58
|
+
"netmask6_Ethernet": "ffff:ffff:ffff:ffff::",
|
59
|
+
"netmask_Ethernet": "255.255.255.0",
|
60
|
+
"network": "10.0.2.0",
|
61
|
+
"network6": "fe80::%12",
|
62
|
+
"network6_Ethernet": "fe80::%12",
|
63
|
+
"network_Ethernet": "10.0.2.0",
|
64
|
+
"networking": {
|
65
|
+
"dhcp": "10.0.2.2",
|
66
|
+
"fqdn": "WIN-E5K8TM30719",
|
67
|
+
"hostname": "WIN-E5K8TM30719",
|
68
|
+
"interfaces": {
|
69
|
+
"Ethernet": {
|
70
|
+
"bindings": [
|
71
|
+
{
|
72
|
+
"address": "10.0.2.15",
|
73
|
+
"netmask": "255.255.255.0",
|
74
|
+
"network": "10.0.2.0"
|
75
|
+
}
|
76
|
+
],
|
77
|
+
"bindings6": [
|
78
|
+
{
|
79
|
+
"address": "fe80::a180:36e0:3a6e:1005%12",
|
80
|
+
"netmask": "ffff:ffff:ffff:ffff::",
|
81
|
+
"network": "fe80::%12"
|
82
|
+
}
|
83
|
+
],
|
84
|
+
"dhcp": "10.0.2.2",
|
85
|
+
"ip": "10.0.2.15",
|
86
|
+
"ip6": "fe80::a180:36e0:3a6e:1005%12",
|
87
|
+
"mac": "08:00:27:81:38:FA",
|
88
|
+
"mtu": 1500,
|
89
|
+
"netmask": "255.255.255.0",
|
90
|
+
"netmask6": "ffff:ffff:ffff:ffff::",
|
91
|
+
"network": "10.0.2.0",
|
92
|
+
"network6": "fe80::%12"
|
93
|
+
}
|
94
|
+
},
|
95
|
+
"ip": "10.0.2.15",
|
96
|
+
"ip6": "fe80::a180:36e0:3a6e:1005%12",
|
97
|
+
"mac": "08:00:27:81:38:FA",
|
98
|
+
"mtu": 1500,
|
99
|
+
"netmask": "255.255.255.0",
|
100
|
+
"netmask6": "ffff:ffff:ffff:ffff::",
|
101
|
+
"network": "10.0.2.0",
|
102
|
+
"network6": "fe80::%12",
|
103
|
+
"primary": "Ethernet"
|
104
|
+
},
|
105
|
+
"operatingsystem": "windows",
|
106
|
+
"operatingsystemmajrelease": "2012 R2",
|
107
|
+
"operatingsystemrelease": "2012 R2",
|
108
|
+
"os": {
|
109
|
+
"architecture": "x64",
|
110
|
+
"family": "windows",
|
111
|
+
"hardware": "x86_64",
|
112
|
+
"name": "windows",
|
113
|
+
"release": {
|
114
|
+
"full": "2012 R2",
|
115
|
+
"major": "2012 R2"
|
116
|
+
},
|
117
|
+
"windows": {
|
118
|
+
"system32": "C:\\Windows\\system32"
|
119
|
+
}
|
120
|
+
},
|
121
|
+
"osfamily": "windows",
|
122
|
+
"path": "C:/Program Files/Puppet Labs/Puppet/facter/bin;C:\\Program Files\\Puppet Labs\\Puppet\\puppet\\bin;C:\\Program Files\\Puppet Labs\\Puppet\\facter\\bin;C:\\Program Files\\Puppet Labs\\Puppet\\hiera\\bin;C:\\Program Files\\Puppet Labs\\Puppet\\mcollective\\bin;C:\\Program Files\\Puppet Labs\\Puppet\\bin;C:\\Program Files\\Puppet Labs\\Puppet\\sys\\ruby\\bin;C:\\Program Files\\Puppet Labs\\Puppet\\sys\\tools\\bin;C:\\Windows\\system32;C:\\Windows;C:\\Windows\\System32\\Wbem;C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\;C:\\Program Files (x86)\\Git\\cmd;C:\\Program Files (x86)\\Git\\bin;C:\\Program Files\\Puppet Labs\\Puppet\\bin",
|
123
|
+
"physicalprocessorcount": 1,
|
124
|
+
"processor0": "Intel(R) Core(TM) i7-4850HQ CPU @ 2.30GHz",
|
125
|
+
"processorcount": 1,
|
126
|
+
"processors": {
|
127
|
+
"count": 1,
|
128
|
+
"isa": "x64",
|
129
|
+
"models": [
|
130
|
+
"Intel(R) Core(TM) i7-4850HQ CPU @ 2.30GHz"
|
131
|
+
],
|
132
|
+
"physicalcount": 1
|
133
|
+
},
|
134
|
+
"productname": "VirtualBox",
|
135
|
+
"puppetversion": "4.2.3",
|
136
|
+
"ruby": {
|
137
|
+
"platform": "x64-mingw32",
|
138
|
+
"sitedir": "C:/Program Files/Puppet Labs/Puppet/sys/ruby/lib/ruby/site_ruby/2.1.0",
|
139
|
+
"version": "2.1.7"
|
140
|
+
},
|
141
|
+
"rubyplatform": "x64-mingw32",
|
142
|
+
"rubysitedir": "C:/Program Files/Puppet Labs/Puppet/sys/ruby/lib/ruby/site_ruby/2.1.0",
|
143
|
+
"rubyversion": "2.1.7",
|
144
|
+
"serialnumber": "0",
|
145
|
+
"system32": "C:\\Windows\\system32",
|
146
|
+
"system_uptime": {
|
147
|
+
"days": 0,
|
148
|
+
"hours": 0,
|
149
|
+
"seconds": 287,
|
150
|
+
"uptime": "0:04 hours"
|
151
|
+
},
|
152
|
+
"timezone": "Coordinated Universal Time",
|
153
|
+
"uptime": "0:04 hours",
|
154
|
+
"uptime_days": 0,
|
155
|
+
"uptime_hours": 0,
|
156
|
+
"uptime_seconds": 287,
|
157
|
+
"virtual": "virtualbox",
|
158
|
+
"clientcert": "win-e5k8tm30719",
|
159
|
+
"clientversion": "4.2.3",
|
160
|
+
"clientnoop": false
|
161
|
+
},
|
162
|
+
"timestamp": "2015-11-19T01:47:05.215450000+00:00",
|
163
|
+
"expiration": "2015-11-19T02:17:05.215450000+00:00"
|
164
|
+
}
|
data/lib/controlrepo.rb
CHANGED
@@ -90,7 +90,7 @@ class Controlrepo
|
|
90
90
|
@environment_conf = File.expand_path('./environment.conf',@root)
|
91
91
|
@facts_dir = File.expand_path('./spec/factsets',@root)
|
92
92
|
@spec_dir = File.expand_path('./spec',@root)
|
93
|
-
@facts_files = Dir["#{@facts_dir}/*.json"]
|
93
|
+
@facts_files = [Dir["#{@facts_dir}/*.json"],Dir["#{File.expand_path('../../factsets',__FILE__)}/*.json"]].flatten
|
94
94
|
@nodeset_file = File.expand_path('./spec/acceptance/nodesets/controlrepo-nodes.yml',@root)
|
95
95
|
@role_regex = /role[s]?:{2}/
|
96
96
|
@profile_regex = /profile[s]?:{2}/
|
@@ -99,6 +99,17 @@ class Controlrepo
|
|
99
99
|
$temp_modulepath = nil
|
100
100
|
end
|
101
101
|
|
102
|
+
def to_s
|
103
|
+
<<-END.gsub(/^\s{4}/,'')
|
104
|
+
puppetfile: #{@puppetfile}
|
105
|
+
environment_conf: #{@environment_conf}
|
106
|
+
facts_dir: #{@facts_dir}
|
107
|
+
spec_dir: #{@spec_dir}
|
108
|
+
facts_files: #{@facts_files}
|
109
|
+
nodeset_file: #{@nodeset_file}
|
110
|
+
END
|
111
|
+
end
|
112
|
+
|
102
113
|
def roles
|
103
114
|
classes.keep_if { |c| c =~ @role_regex }
|
104
115
|
end
|
@@ -214,7 +225,11 @@ class Controlrepo
|
|
214
225
|
end
|
215
226
|
|
216
227
|
def hiera_config
|
217
|
-
|
228
|
+
begin
|
229
|
+
YAML.load_file(hiera_config_file)
|
230
|
+
rescue TypeError
|
231
|
+
raise "Could not load hiera config file: #{hiera_config_file}"
|
232
|
+
end
|
218
233
|
end
|
219
234
|
|
220
235
|
def hiera_config=(data)
|
data/lib/controlrepo/beaker.rb
CHANGED
@@ -24,7 +24,7 @@ class Controlrepo
|
|
24
24
|
begin
|
25
25
|
if facts['os']['distro']['id'] == 'Debian'
|
26
26
|
os = 'Debian'
|
27
|
-
version = facts['os']['distro']['release']['major']
|
27
|
+
version = "#{facts['os']['distro']['release']['major']}.#{facts['os']['distro']['release']['minor']}"
|
28
28
|
end
|
29
29
|
rescue
|
30
30
|
# Do nothing
|
@@ -69,7 +69,7 @@ class Controlrepo
|
|
69
69
|
version = facts['os']['release']['major']
|
70
70
|
end
|
71
71
|
rescue
|
72
|
-
# Do nothing, this is the easiest way to handle the hash
|
72
|
+
# Do nothing, this is the easiest way to handle the hash being in different formats
|
73
73
|
end
|
74
74
|
|
75
75
|
begin
|
@@ -78,13 +78,13 @@ class Controlrepo
|
|
78
78
|
version = facts['os']['distro']['release']['major']
|
79
79
|
end
|
80
80
|
rescue
|
81
|
-
# Do nothing, this is the easiest way to handle the hash
|
81
|
+
# Do nothing, this is the easiest way to handle the hash being in different formats
|
82
82
|
end
|
83
83
|
|
84
84
|
begin
|
85
85
|
if facts['os']['distro']['id'] == 'Debian'
|
86
|
-
platform = '
|
87
|
-
version = facts['os']['distro']['release']['
|
86
|
+
platform = 'debian'
|
87
|
+
version = facts['os']['distro']['release']['full']
|
88
88
|
end
|
89
89
|
rescue
|
90
90
|
# Do nothing
|
@@ -24,6 +24,20 @@ task :hiera_setup do
|
|
24
24
|
repo.hiera_config = current_config
|
25
25
|
end
|
26
26
|
|
27
|
+
task :controlrepo_details do
|
28
|
+
require 'controlrepo'
|
29
|
+
puts Controlrepo.new.to_s
|
30
|
+
end
|
31
|
+
|
32
|
+
task :generate_controlrepo_yaml do
|
33
|
+
require 'controlrepo'
|
34
|
+
repo = Controlrepo.new
|
35
|
+
template_dir = File.expand_path('../../templates',File.dirname(__FILE__))
|
36
|
+
controlrepo_yaml_template = File.read(File.expand_path('./controlrepo.yaml.erb',template_dir))
|
37
|
+
puts ERB.new(controlrepo_yaml_template, nil, '-').result(binding)
|
38
|
+
end
|
39
|
+
|
40
|
+
|
27
41
|
task :generate_nodesets do
|
28
42
|
require 'controlrepo/beaker'
|
29
43
|
require 'net/http'
|
@@ -31,47 +45,33 @@ task :generate_nodesets do
|
|
31
45
|
|
32
46
|
repo = Controlrepo.new
|
33
47
|
|
34
|
-
|
35
|
-
Dir.mkdir("#{repo.root}/spec/acceptance")
|
36
|
-
puts "Created #{repo.root}/spec/acceptance"
|
37
|
-
rescue Errno::EEXIST
|
38
|
-
# Do nothing, this is okay
|
39
|
-
end
|
48
|
+
puts "HOSTS:"
|
40
49
|
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
# Do nothing, this is okay
|
46
|
-
end
|
47
|
-
|
48
|
-
facts = repo.facts
|
49
|
-
facts.each do |fact_set|
|
50
|
-
boxname = Controlrepo_beaker.facts_to_vagrant_box(fact_set)
|
51
|
-
platform = Controlrepo_beaker.facts_to_platform(fact_set)
|
50
|
+
repo.facts.each do |fact_set|
|
51
|
+
node_name = File.basename(repo.facts_files[repo.facts.index(fact_set)],'.json')
|
52
|
+
boxname = Controlrepo::Beaker.facts_to_vagrant_box(fact_set)
|
53
|
+
platform = Controlrepo::Beaker.facts_to_platform(fact_set)
|
52
54
|
response = Net::HTTP.get(URI.parse("https://atlas.hashicorp.com/api/v1/box/#{boxname}"))
|
53
55
|
url = 'URL goes here'
|
54
56
|
|
55
|
-
|
57
|
+
if response =~ /Not Found/i
|
58
|
+
comment_out = true
|
59
|
+
else
|
60
|
+
comment_out = false
|
56
61
|
box_info = JSON.parse(response)
|
57
62
|
box_info['current_version']['providers'].each do |provider|
|
58
|
-
if
|
63
|
+
if provider['name'] == 'virtualbox'
|
59
64
|
url = provider['original_url']
|
60
65
|
end
|
61
66
|
end
|
62
67
|
end
|
63
68
|
|
64
|
-
# Use an ERB template
|
69
|
+
# Use an ERB template
|
65
70
|
template_dir = File.expand_path('../../templates',File.dirname(__FILE__))
|
66
71
|
fixtures_template = File.read(File.expand_path('./nodeset.yaml.erb',template_dir))
|
67
|
-
|
68
|
-
if File.exists?(output_file) == false
|
69
|
-
File.write(output_file,ERB.new(fixtures_template, nil, '-').result(binding))
|
70
|
-
puts "Created #{output_file}"
|
71
|
-
else
|
72
|
-
puts "#{output_file} already exists, not going to overwrite because scared"
|
73
|
-
end
|
72
|
+
puts ERB.new(fixtures_template, nil, '-').result(binding)
|
74
73
|
end
|
74
|
+
|
75
75
|
end
|
76
76
|
|
77
77
|
task :controlrepo_autotest_prep do
|
@@ -121,6 +121,7 @@ task :controlrepo_autotest_prep do
|
|
121
121
|
end
|
122
122
|
end
|
123
123
|
end
|
124
|
+
binding.pry
|
124
125
|
File.write("#{@repo.temp_environmentpath}/#{@config.environment}/hiera.yaml",hiera_config.to_yaml)
|
125
126
|
|
126
127
|
@config.create_fixtures_symlinks(@repo)
|
@@ -2,6 +2,7 @@ require 'controlrepo/class'
|
|
2
2
|
require 'controlrepo/node'
|
3
3
|
require 'controlrepo/group'
|
4
4
|
require 'controlrepo/test'
|
5
|
+
require 'git'
|
5
6
|
|
6
7
|
class Controlrepo
|
7
8
|
class TestConfig
|
@@ -99,26 +100,33 @@ class Controlrepo
|
|
99
100
|
puppetcode.join("\n")
|
100
101
|
end
|
101
102
|
|
103
|
+
def checkout_branch(working_dir, branch)
|
104
|
+
Dir.chdir(working_dir)
|
105
|
+
g = Git.open(working_dir)
|
106
|
+
|
107
|
+
# if we are already on the right branch do nothing
|
108
|
+
if ! g.branch.current == @environment then
|
109
|
+
if g.branches.include? branch
|
110
|
+
g.branch(@environment).checkout
|
111
|
+
else
|
112
|
+
puts "Unable to checkout requested environment #{@environment}: branch not found"
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
102
117
|
def r10k_deploy_local(repo = Controlrepo.new)
|
103
118
|
require 'controlrepo'
|
104
119
|
tempdir = Dir.mktmpdir('r10k')
|
105
120
|
repo.tempdir = tempdir
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
r10k_config[:sources].map do |name,source_settings|
|
112
|
-
source_settings["basedir"] = "#{tempdir}#{source_settings["basedir"]}"
|
113
|
-
FileUtils::mkdir_p(source_settings["basedir"])
|
114
|
-
# Yes, I realise this is going to set it many times
|
115
|
-
repo.temp_environmentpath = source_settings["basedir"]
|
116
|
-
end
|
117
|
-
File.write("#{tempdir}/r10k.yaml",r10k_config.to_yaml)
|
121
|
+
temp_code_dir = "#{tempdir}/etc/puppetlabs/code/environments/#{@environment}"
|
122
|
+
repo.temp_environmentpath = temp_code_dir.chomp("/#{@environment}")
|
123
|
+
FileUtils.mkdir_p(temp_code_dir)
|
124
|
+
FileUtils.cp_r("#{Dir.pwd}/.", temp_code_dir)
|
125
|
+
checkout_branch(temp_code_dir, @environment)
|
118
126
|
|
119
127
|
# Pull the trigger!
|
120
|
-
Dir.chdir(
|
121
|
-
|
128
|
+
Dir.chdir(temp_code_dir) do
|
129
|
+
system("r10k puppetfile install --verbose")
|
122
130
|
end
|
123
131
|
|
124
132
|
# Return tempdir for use
|
@@ -190,4 +198,4 @@ class Controlrepo
|
|
190
198
|
# with the output from the templated tests, us ethe better one
|
191
199
|
# bearing in minf that beaker has logger options that could help
|
192
200
|
end
|
193
|
-
end
|
201
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
# Classes to be tested
|
2
|
+
classes:
|
3
|
+
<% repo.roles.each do |role| -%>
|
4
|
+
- <%= role %>
|
5
|
+
<% end -%>
|
6
|
+
|
7
|
+
# Nodes to tests classes on, this refers to a 'factset' or 'nodeset'
|
8
|
+
# depending on weather you are running 'spec' or 'acceptance' tests
|
9
|
+
nodes:
|
10
|
+
<% repo.facts_files.each do |file| -%>
|
11
|
+
- <%= File.basename(file,'.json') %>
|
12
|
+
<% end -%>
|
13
|
+
|
14
|
+
# You can group classes here to save typing
|
15
|
+
class_groups:
|
16
|
+
|
17
|
+
# You can group nodes here to save typing
|
18
|
+
# We have created a 'non_windows_nodes' group because we can't
|
19
|
+
# give you Windows vagrant boxes to test with because licensing,
|
20
|
+
# we can give you fact sets though so go crazy with spec testing!
|
21
|
+
node_groups:
|
22
|
+
windows_nodes:
|
23
|
+
<% repo.facts_files.each do |file| -%>
|
24
|
+
<% if File.basename(file,'.json') =~ /Windows/i -%>
|
25
|
+
- <%= File.basename(file,'.json') %>
|
26
|
+
<% end -%>
|
27
|
+
<% end -%>
|
28
|
+
non_windows_nodes:
|
29
|
+
include: 'all_nodes'
|
30
|
+
exclude: 'windows_nodes'
|
31
|
+
|
32
|
+
test_matrix:
|
33
|
+
- all_nodes:
|
34
|
+
classes: 'all_classes'
|
35
|
+
tests: 'spec'
|
36
|
+
- non_windows_nodes:
|
37
|
+
classes: 'all_classes'
|
38
|
+
tests: 'acceptance'
|
data/templates/nodeset.yaml.erb
CHANGED
@@ -1,9 +1,9 @@
|
|
1
|
-
|
2
|
-
<%=
|
3
|
-
roles:
|
4
|
-
- agent
|
5
|
-
type: aio
|
6
|
-
platform: <%= platform %>
|
7
|
-
box: <%= boxname %>
|
8
|
-
box_url: <%= url %>
|
9
|
-
hypervisor: vagrant_virtualbox
|
1
|
+
<% if comment_out then prefix = '#' end-%>
|
2
|
+
<%= prefix %> <%= node_name %>:
|
3
|
+
<%= prefix %> roles:
|
4
|
+
<%= prefix %> - agent
|
5
|
+
<%= prefix %> type: aio
|
6
|
+
<%= prefix %> platform: <%= platform %>
|
7
|
+
<%= prefix %> box: <%= boxname %>
|
8
|
+
<%= prefix %> box_url: <%= url %>
|
9
|
+
<%= prefix %> hypervisor: vagrant_virtualbox
|