kikubari 0.0.3 → 0.0.4
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.travis.yml +5 -0
- data/Gemfile +1 -12
- data/Gemfile.lock +36 -17
- data/README.md +69 -0
- data/Rakefile +6 -34
- data/VERSION +1 -1
- data/bin/kikubari +1 -10
- data/kikubari.gemspec +25 -43
- data/lib/configuration/deploy_configuration.rb +19 -4
- data/lib/deploy_dir.rb +8 -8
- data/lib/deploy_logger.rb +28 -24
- data/lib/deployer/deployer.rb +41 -44
- data/lib/deployer/deployer_git.rb +1 -2
- data/lib/kikubari.rb +17 -8
- data/{test → spec}/deploy_files/databases.yml +0 -0
- data/{test → spec}/deploy_files/deploy.yml +0 -0
- data/{test → spec}/deploy_files/deploy_git.yml +5 -5
- data/{test → spec}/deploy_files/deploy_git_test_file.yml +5 -5
- data/{test → spec}/deploy_files/deploy_symlink.yml +6 -5
- data/{test → spec}/deploy_files/empty.yml +0 -0
- data/{test → spec}/deploy_files/one_file_test.yml +0 -0
- data/{test → spec}/deploy_files/one_folder.yml +0 -0
- data/spec/lib/deploy_spec.rb +146 -0
- data/spec/lib/deployers/git.rb +32 -0
- data/spec/spec_helper.rb +23 -0
- metadata +88 -126
- data/README.rdoc +0 -25
- data/lib/deployer/deployer_git_symfony.rb +0 -15
- data/test/helper.rb +0 -18
- data/test/lib/deploy_spec.rb +0 -94
- data/test/lib/deployers/git.rb +0 -29
- data/test/lib/deployers/mysql_backup.rb +0 -7
- data/test/lib/deployers/symfony_git.rb +0 -29
- data/test/spec_helper.rb +0 -20
data/lib/deployer/deployer.rb
CHANGED
@@ -5,67 +5,65 @@
|
|
5
5
|
# On deployment config (<em>deploy.yml</em>) can be setted the actions to be done one the code gathering is successfull. Then tipically the system would make come backups task, link some folder and files.
|
6
6
|
#
|
7
7
|
# Author:: Jose A Pio (mailto:josetonyp@gmail.com)
|
8
|
-
# Copyright:: Copyright (c) 2011
|
8
|
+
# Copyright:: Copyright (c) 2011
|
9
9
|
# License:: Distributes under the same terms as Ruby
|
10
10
|
module Kikubari
|
11
|
-
|
12
11
|
class Deploy
|
13
|
-
|
14
12
|
class Deployer
|
15
|
-
|
13
|
+
|
16
14
|
attr_accessor :config
|
17
|
-
|
15
|
+
|
18
16
|
def initialize(config)
|
19
17
|
@config = config
|
20
18
|
@logger = Logger.new
|
21
19
|
end
|
22
|
-
|
20
|
+
|
23
21
|
##
|
24
22
|
# Create te the folder structure as is in the YAML
|
25
23
|
#
|
26
24
|
def create_structure
|
27
25
|
@config.do["folder_structure"].each do |command|
|
28
26
|
unless File.directory? command.first[1]
|
29
|
-
|
27
|
+
@logger.print "Creating Structure folder: #{command.first[1]}"
|
30
28
|
FileUtils.mkdir_p command.first[1]
|
31
29
|
end
|
32
30
|
end
|
33
31
|
self
|
34
32
|
end
|
35
|
-
|
36
|
-
|
33
|
+
|
34
|
+
|
37
35
|
##
|
38
36
|
# Create the environment folder as requested in the configuration
|
39
37
|
#
|
40
38
|
def create_environment_folder
|
41
39
|
environment = @config.environment
|
42
40
|
unless File.directory? @config.environment_folder
|
43
|
-
|
41
|
+
@logger.print "Creating Environment folder: #{ @config.environment_folder}"
|
44
42
|
FileUtils.mkdir_p @config.environment_folder
|
45
43
|
end
|
46
44
|
self
|
47
45
|
end
|
48
|
-
|
46
|
+
|
49
47
|
##
|
50
|
-
# Create the folder where you will deploy the actual version of the code based on the configuration
|
48
|
+
# Create the folder where you will deploy the actual version of the code based on the configuration
|
51
49
|
#
|
52
50
|
def create_version_folder
|
53
51
|
FileUtils.mkdir_p(@config.env_time_folder) unless File.directory? @config.env_time_folder
|
54
52
|
self
|
55
53
|
end
|
56
|
-
|
54
|
+
|
57
55
|
##
|
58
56
|
# Create the current symlink to the deploy version folder
|
59
57
|
#
|
60
58
|
def create_current_symlink_folder
|
61
|
-
destination = @config.current_deploy_folder
|
59
|
+
destination = @config.current_deploy_folder
|
62
60
|
origin = Pathname.new( @config.env_time_folder ).relative_path_from( Pathname.new( @config.current_deploy_folder.gsub(/\/[^\/]*?$/, "") ) ).to_s
|
63
61
|
FileUtils.rm_f(destination) if File.symlink?(destination)
|
64
62
|
FileUtils.ln_s origin, destination
|
65
63
|
self
|
66
64
|
end
|
67
|
-
|
68
|
-
|
65
|
+
|
66
|
+
|
69
67
|
##
|
70
68
|
# Test if selected file already exist
|
71
69
|
#
|
@@ -78,39 +76,39 @@ module Kikubari
|
|
78
76
|
end
|
79
77
|
self
|
80
78
|
end
|
81
|
-
|
79
|
+
|
82
80
|
##
|
83
81
|
# Create the Symlinked folders
|
84
82
|
#
|
85
83
|
def create_sylinked_folders
|
86
84
|
@config.do["folder_symbolic_links"].each do |folder|
|
87
|
-
|
88
|
-
raise "Folder: #{@config.env_time_folder}/#{folder[1]} already exists and the symlink can't be created" if File.directory?("#{@config.env_time_folder}/#{folder[1]}")
|
89
|
-
create_symlink( folder )
|
85
|
+
@logger.print "- linking: #{@config.env_time_folder}/#{folder[1]}"
|
86
|
+
raise "Folder: #{@config.env_time_folder}/#{folder[1]} already exists and the symlink can't be created" if File.directory?("#{@config.env_time_folder}/#{folder[1]}")
|
87
|
+
create_symlink( folder )
|
90
88
|
end
|
91
89
|
self
|
92
90
|
end
|
93
|
-
|
91
|
+
|
94
92
|
##
|
95
93
|
# Execute creation of symlinked folder
|
96
94
|
#
|
97
95
|
def create_symlink( folder )
|
98
96
|
destination = "#{@config.env_time_folder}/#{folder[1]}"
|
99
|
-
raise ArgumentError , "Origin folder #{
|
97
|
+
raise ArgumentError , "Origin folder #{@config.get_structure_folder(folder[0])} is not a valid folder" unless File.directory?("#{@config.get_structure_folder(folder[0])}")
|
100
98
|
origin = Pathname.new( "#{@config.get_structure_folder(folder[0])}" ).relative_path_from( Pathname.new( destination.gsub(/\/[^\/]*?$/, "") ) ).to_s ## Origin as a relative path from destination
|
101
99
|
FileUtils.ln_s origin, destination
|
102
100
|
end
|
103
|
-
|
101
|
+
|
104
102
|
def create_symlinked_files
|
105
|
-
|
103
|
+
@logger.print "Creating Files symbolic links"
|
106
104
|
@config.do["file_symbolic_link"].each do |folder|
|
107
105
|
destination = "#{@config.env_time_folder}/#{folder[1]}"
|
108
106
|
origin = Pathname.new( "#{@config.get_test_file(folder[0])}" ).relative_path_from(Pathname.new( destination.gsub(/\/[^\/]*?$/, "") )).to_s
|
109
107
|
FileUtils.ln_s origin , destination
|
110
108
|
end
|
111
109
|
end
|
112
|
-
|
113
|
-
|
110
|
+
|
111
|
+
|
114
112
|
##
|
115
113
|
# Create deployment structure
|
116
114
|
#
|
@@ -119,19 +117,19 @@ module Kikubari
|
|
119
117
|
create_environment_folder.create_version_folder
|
120
118
|
self
|
121
119
|
end
|
122
|
-
|
120
|
+
|
123
121
|
##
|
124
122
|
# Rotate old version folders
|
125
123
|
#
|
126
124
|
def rotate_folders
|
127
125
|
DeployDir.rotate_folders( @config.environment_folder , @config.config["history_limit"] )
|
128
126
|
end
|
129
|
-
|
130
|
-
|
127
|
+
|
128
|
+
|
131
129
|
def has_after_deploy_run_commands
|
132
130
|
@config.after.has_key?("run") && !@config.after["run"].empty?
|
133
131
|
end
|
134
|
-
|
132
|
+
|
135
133
|
##
|
136
134
|
# Run comand line script after deploy
|
137
135
|
#
|
@@ -144,12 +142,12 @@ module Kikubari
|
|
144
142
|
end
|
145
143
|
out
|
146
144
|
end
|
147
|
-
|
148
|
-
|
145
|
+
|
146
|
+
|
149
147
|
def has_before_deploy_run_commands
|
150
148
|
@config.before.has_key?("run") && !@config.before["run"].empty?
|
151
149
|
end
|
152
|
-
|
150
|
+
|
153
151
|
def before_deploy_run
|
154
152
|
return unless has_before_deploy_run_commands
|
155
153
|
out = Array.new
|
@@ -159,21 +157,21 @@ module Kikubari
|
|
159
157
|
end
|
160
158
|
out
|
161
159
|
end
|
162
|
-
|
160
|
+
|
163
161
|
def execute_shell(command)
|
164
|
-
@logger.run(command, @config.env_time_folder)
|
162
|
+
@logger.run(command, @config.env_time_folder)
|
165
163
|
temp = capture_stderr "cd #{@config.env_time_folder} ; #{command} "
|
166
164
|
@logger.result(temp[:stdout]) if temp[:stdout] != ""
|
167
165
|
@logger.error(temp[:stderr]) if temp[:stderr] != ""
|
168
166
|
temp
|
169
167
|
end
|
170
|
-
|
171
|
-
|
168
|
+
|
169
|
+
|
172
170
|
def capture_stderr ( command )
|
173
171
|
stdin, stdout, stderr = Open3.popen3( command )
|
174
172
|
({ :stdout => stdout.readlines.join(""), :stderr => stderr.readlines.join("") })
|
175
173
|
end
|
176
|
-
|
174
|
+
|
177
175
|
##
|
178
176
|
# Execute the deploy
|
179
177
|
#
|
@@ -185,18 +183,17 @@ module Kikubari
|
|
185
183
|
create_sylinked_folders if @config.do.has_key?("folder_symbolic_links") && !@config.do["folder_symbolic_links"].empty?
|
186
184
|
create_symlinked_files if @config.do.has_key?("file_symbolic_link") && !@config.do["file_symbolic_link"].empty?
|
187
185
|
create_current_symlink_folder
|
188
|
-
rotate_folders
|
186
|
+
rotate_folders
|
189
187
|
after_deploy_run
|
190
188
|
self
|
191
189
|
end
|
192
|
-
|
190
|
+
|
193
191
|
private
|
194
|
-
|
192
|
+
|
195
193
|
def do_deploy
|
196
194
|
raise "This is an abstract method, implement this method in your deployer"
|
197
|
-
|
198
195
|
end
|
199
|
-
|
196
|
+
|
200
197
|
end
|
201
198
|
end
|
202
|
-
end
|
199
|
+
end
|
@@ -15,10 +15,9 @@ module Kikubari
|
|
15
15
|
def do_deploy
|
16
16
|
branch = @config.config["branch"] || "master"
|
17
17
|
%x(git clone #{@config.config["origin"]} -b #{branch} #{@config.env_time_folder} )
|
18
|
+
FileUtils.rm_rf("#{@config.env_time_folder}/.git")
|
18
19
|
end
|
19
20
|
|
20
21
|
end
|
21
|
-
|
22
22
|
end
|
23
|
-
|
24
23
|
end
|
data/lib/kikubari.rb
CHANGED
@@ -1,5 +1,16 @@
|
|
1
|
-
|
1
|
+
require 'fileutils'
|
2
|
+
require 'yaml'
|
3
|
+
require 'git'
|
4
|
+
require 'open3'
|
5
|
+
|
6
|
+
require "configuration/deploy_configuration"
|
7
|
+
require "deployer/deployer"
|
8
|
+
require "deployer/deployer_git"
|
9
|
+
require "deployer/deployer_git_wordpress"
|
10
|
+
require "deploy_dir"
|
11
|
+
require "deploy_logger"
|
2
12
|
|
13
|
+
module Kikubari
|
3
14
|
class Deploy
|
4
15
|
|
5
16
|
attr_accessor :config
|
@@ -11,26 +22,24 @@ module Kikubari
|
|
11
22
|
end
|
12
23
|
|
13
24
|
def deploy
|
14
|
-
|
15
|
-
deployer.create_deploy_structure.deploy
|
25
|
+
get_deployer( @config ).create_deploy_structure.deploy
|
16
26
|
end
|
17
27
|
|
18
28
|
def rollback
|
19
|
-
|
29
|
+
@logger.print "rollingback"
|
20
30
|
end
|
21
31
|
|
22
32
|
def change( version )
|
23
|
-
|
33
|
+
@logger.print "changing to version #{version}"
|
24
34
|
end
|
25
35
|
|
26
36
|
private
|
27
37
|
|
28
38
|
def get_deployer config
|
29
|
-
deployer_class
|
30
|
-
eval(deployer_class).new config
|
39
|
+
eval(deployer_class).new(config)
|
31
40
|
end
|
32
41
|
|
33
42
|
|
34
43
|
end
|
35
44
|
|
36
|
-
end
|
45
|
+
end
|
File without changes
|
File without changes
|
@@ -1,20 +1,20 @@
|
|
1
1
|
## Configurations Parameters
|
2
2
|
config:
|
3
3
|
system: git
|
4
|
-
origin: "git@github.com:josetonyp/
|
4
|
+
origin: "git@github.com:josetonyp/elements.git"
|
5
5
|
branch: master
|
6
6
|
history_limit: 10
|
7
|
-
|
7
|
+
|
8
8
|
## Task actions for deployers
|
9
|
-
## At versión 1 deployers should know the task order
|
9
|
+
## At versión 1 deployers should know the task order
|
10
10
|
do:
|
11
11
|
|
12
12
|
# Folder structure defining the backup and mantainance folder behind the deployment process
|
13
13
|
folder_structure:
|
14
14
|
cache: 'cache/#{environment}'
|
15
15
|
log: 'log/#{environment}'
|
16
|
-
|
16
|
+
|
17
17
|
# Folder links from project folder and matainance folders
|
18
18
|
folder_symbolic_links:
|
19
19
|
cache: cache
|
20
|
-
log: log
|
20
|
+
log: log
|
@@ -1,19 +1,19 @@
|
|
1
1
|
## Configurations Parameters
|
2
2
|
config:
|
3
3
|
system: git
|
4
|
-
origin: "git@github.com:josetonyp/
|
4
|
+
origin: "git@github.com:josetonyp/elements.git"
|
5
5
|
branch: master
|
6
6
|
history_limit: 10
|
7
|
-
|
7
|
+
|
8
8
|
## Task actions for deployers
|
9
|
-
## At versión 1 deployers should know the task order
|
9
|
+
## At versión 1 deployers should know the task order
|
10
10
|
do:
|
11
11
|
|
12
12
|
# Folder structure defining the backup and mantainance folder behind the deployment process
|
13
13
|
folder_structure:
|
14
14
|
cache: 'cache/#{environment}'
|
15
15
|
log: 'log/#{environment}'
|
16
|
-
|
16
|
+
|
17
17
|
# Folder links from project folder and matainance folders
|
18
18
|
folder_symbolic_links:
|
19
19
|
cache: cache
|
@@ -25,4 +25,4 @@ do:
|
|
25
25
|
|
26
26
|
# Links to be build from tested files. Only tested files can be converted in symlinks to the project
|
27
27
|
file_symbolic_link:
|
28
|
-
test: 'test/test.yml'
|
28
|
+
test: 'test/test.yml'
|
@@ -1,15 +1,15 @@
|
|
1
1
|
# Deploy example sheet for a Symfony 1.4 and MySQL project
|
2
2
|
#
|
3
3
|
# Author:: Jose A Pio (mailto:josetonyp@gmail.com)
|
4
|
-
# Copyright:: Copyright (c) 2011
|
4
|
+
# Copyright:: Copyright (c) 2011
|
5
5
|
# License:: Distributes under the same terms as Ruby
|
6
6
|
#
|
7
7
|
|
8
8
|
## Configurations Parameters
|
9
9
|
config:
|
10
|
-
|
10
|
+
|
11
11
|
## Task actions for deployers
|
12
|
-
## At versión 1 deployers should know the task order
|
12
|
+
## At versión 1 deployers should know the task order
|
13
13
|
do:
|
14
14
|
|
15
15
|
# Folder structure defining the backup and mantainance folder behind the deployment process
|
@@ -21,7 +21,8 @@ do:
|
|
21
21
|
folder_symbolic_links:
|
22
22
|
#name: #destination_name
|
23
23
|
cache: cache
|
24
|
-
|
24
|
+
config: config
|
25
|
+
|
25
26
|
# Files to be tested before deployment. This files must exists or deployment will rise an exception
|
26
27
|
# named_file: origin
|
27
28
|
test_files:
|
@@ -34,4 +35,4 @@ do:
|
|
34
35
|
|
35
36
|
after:
|
36
37
|
run:
|
37
|
-
- 'mkdir new_folder'
|
38
|
+
- 'mkdir new_folder'
|
File without changes
|
File without changes
|
File without changes
|
@@ -0,0 +1,146 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Kikubari::Deploy::Deployer do
|
4
|
+
|
5
|
+
let(:config) do
|
6
|
+
Kikubari::Deploy::Configuration.new(
|
7
|
+
"#{RSpec.configuration.deploy_files}/deploy_symlink.yml",
|
8
|
+
deploy_folder: RSpec.configuration.target_folder,
|
9
|
+
debug: false,
|
10
|
+
dry_run: false,
|
11
|
+
environment: 'production',
|
12
|
+
rollback: false)
|
13
|
+
end
|
14
|
+
|
15
|
+
subject(:subject) { described_class.new(config) }
|
16
|
+
|
17
|
+
before :all do
|
18
|
+
clear_target_project
|
19
|
+
end
|
20
|
+
|
21
|
+
context 'Folder Structure' do
|
22
|
+
|
23
|
+
it "creates an environment folder to host the structure" do
|
24
|
+
expect(subject.create_environment_folder).to satisfy do |deployer|
|
25
|
+
File.directory?(deployer.config.environment_folder)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
it "creates a target version folder with a the current time stamp as name" do
|
30
|
+
expect(subject.create_version_folder).to satisfy do |deployer|
|
31
|
+
File.directory?(deployer.config.env_time_folder)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
it "createa a symlink 'current' to the actual target version folder" do
|
36
|
+
expect(subject.create_version_folder.create_current_symlink_folder).to satisfy do |deployer|
|
37
|
+
File.symlink?(deployer.config.current_deploy_folder)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
# Giving a folder structure like:
|
42
|
+
# From file: spec/deploy_files/deploy_symlink.yml
|
43
|
+
#
|
44
|
+
# folder_structure:
|
45
|
+
# cache: 'cache/#{environment}'
|
46
|
+
# config: 'config/#{environment}'
|
47
|
+
#
|
48
|
+
# Example:
|
49
|
+
# project:
|
50
|
+
# cache
|
51
|
+
# [environment]
|
52
|
+
# config
|
53
|
+
# [environment]
|
54
|
+
# [environment]
|
55
|
+
# [version_folder]
|
56
|
+
# cache: Symlink to cache/[environment]
|
57
|
+
# config: Symlink to config/[environment]
|
58
|
+
# current: Symlink to [version_folder]
|
59
|
+
#
|
60
|
+
it "creates the cache and condig folder inside" do
|
61
|
+
subject.tap do |deployer|
|
62
|
+
deployer.create_environment_folder
|
63
|
+
deployer.create_version_folder
|
64
|
+
deployer.create_current_symlink_folder
|
65
|
+
|
66
|
+
expect(deployer.create_structure).to satisfy do
|
67
|
+
File.directory?("#{deployer.config.deploy_folder}/cache/#{deployer.config.environment}") &&
|
68
|
+
File.directory?("#{deployer.config.deploy_folder}/config/#{deployer.config.environment}")
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
it "creates a symlink to inside the version folder pointing to the corresponding folder inside the structure" do
|
74
|
+
subject.tap do |deployer|
|
75
|
+
deployer.create_environment_folder
|
76
|
+
deployer.create_version_folder
|
77
|
+
deployer.create_structure
|
78
|
+
|
79
|
+
expect(deployer.create_sylinked_folders).to satisfy do
|
80
|
+
File.symlink?( "#{deployer.config.env_time_folder}/cache") &&
|
81
|
+
File.symlink?( "#{deployer.config.env_time_folder}/config")
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
it "should not create the symlinked folder is folder already exist" do
|
87
|
+
subject.create_environment_folder.create_version_folder.create_current_symlink_folder
|
88
|
+
subject.create_structure
|
89
|
+
FileUtils.mkdir_p "#{config.env_time_folder}/cache"
|
90
|
+
expect {
|
91
|
+
subject.create_sylinked_folders
|
92
|
+
}.to raise_error
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
context 'File linking' do
|
97
|
+
it "verifies a file in the folder and raise an error" do
|
98
|
+
subject.create_environment_folder.create_version_folder.create_current_symlink_folder
|
99
|
+
expect{subject.test_files}.to raise_error
|
100
|
+
end
|
101
|
+
|
102
|
+
it "verifies a file in the folder" do
|
103
|
+
subject.create_environment_folder.create_version_folder.create_current_symlink_folder
|
104
|
+
expect{subject.test_files}.to raise_error
|
105
|
+
end
|
106
|
+
|
107
|
+
it "creates a symlink file from tested files" do
|
108
|
+
subject.create_deploy_structure
|
109
|
+
## Faking the config folder that should come with the repository
|
110
|
+
FileUtils.mkdir_p "#{config.env_time_folder}/config"
|
111
|
+
`echo "DB data...." >> #{config.deploy_folder}/config/#{config.environment}/databases.yml`
|
112
|
+
subject.create_symlinked_files.should satisfy do |deployer|
|
113
|
+
File.symlink?( "#{config.env_time_folder}/config/databases.yml")
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
context 'Execution' do
|
119
|
+
it "should execute the after run tasks" do
|
120
|
+
subject.create_deploy_structure
|
121
|
+
FileUtils.mkdir_p "#{config.env_time_folder}/config"
|
122
|
+
`echo "DB data..." >> #{config.deploy_folder}/config/#{config.environment}/databases.yml`
|
123
|
+
subject.after_deploy_run.should satisfy do |deployer|
|
124
|
+
File.directory?("#{config.env_time_folder}/new_folder")
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
it "should capture STDERR messages in a variable is command is not valid" do
|
129
|
+
subject.create_deploy_structure
|
130
|
+
subject.config.after['run'] = [ 'This is not a command' ]
|
131
|
+
out = subject.after_deploy_run
|
132
|
+
out.count.should == 1
|
133
|
+
out[0][:stdout].should == ""
|
134
|
+
out[0][:stderr].should_not == ""
|
135
|
+
end
|
136
|
+
|
137
|
+
it "should not capture STDERR messages in a variable if command is valid" do
|
138
|
+
subject.create_deploy_structure
|
139
|
+
subject.config.after['run'] = [ 'ls -lah' ]
|
140
|
+
out = subject.after_deploy_run
|
141
|
+
out.count.should == 1
|
142
|
+
out[0][:stdout].should_not == ""
|
143
|
+
out[0][:stderr].should == ""
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|