sunzi 1.5.2 → 2.0.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 +5 -5
- data/.travis.yml +1 -1
- data/CHANGELOG.md +7 -0
- data/README.md +14 -37
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/{bin → exe}/sunzi +0 -0
- data/lib/sunzi.rb +18 -22
- data/lib/sunzi/actions.rb +35 -0
- data/lib/sunzi/cli.rb +6 -139
- data/lib/sunzi/command.rb +113 -0
- data/lib/sunzi/core_ext.rb +10 -0
- data/lib/sunzi/dependency.rb +26 -29
- data/lib/sunzi/endpoint.rb +17 -0
- data/lib/sunzi/plugin.rb +17 -0
- data/sunzi.gemspec +9 -9
- data/{lib/templates → templates}/create/.gitignore +0 -0
- data/{lib/templates → templates}/create/files/.gitkeep +0 -0
- data/{lib/templates → templates}/create/install.sh +13 -7
- data/{lib/templates → templates}/create/recipes/sunzi.sh +0 -0
- data/{lib/templates → templates}/create/roles/db.sh +0 -0
- data/{lib/templates → templates}/create/roles/web.sh +0 -0
- data/templates/create/sunzi.yml +27 -0
- data/templates/dependency/gemfile.erb +6 -0
- data/templates/dependency/install.erb +6 -0
- metadata +45 -36
- data/lib/sunzi/cloud.rb +0 -24
- data/lib/sunzi/cloud/base.rb +0 -98
- data/lib/sunzi/cloud/digital_ocean.rb +0 -78
- data/lib/sunzi/cloud/linode.rb +0 -154
- data/lib/sunzi/dns.rb +0 -25
- data/lib/sunzi/dns/base.rb +0 -7
- data/lib/sunzi/dns/linode.rb +0 -26
- data/lib/sunzi/dns/route53.rb +0 -25
- data/lib/sunzi/logger.rb +0 -17
- data/lib/sunzi/utility.rb +0 -17
- data/lib/templates/create/sunzi.yml +0 -30
- data/lib/templates/setup/digital_ocean/digital_ocean.yml +0 -28
- data/lib/templates/setup/linode/linode.yml +0 -42
- data/test/test_cli.rb +0 -40
data/lib/sunzi/dependency.rb
CHANGED
@@ -1,39 +1,36 @@
|
|
1
1
|
module Sunzi
|
2
|
-
class Dependency
|
3
|
-
|
4
|
-
|
5
|
-
'linode' => { :require => 'linode', :version => '>= 0.7.9' },
|
6
|
-
'highline' => { :require => 'highline', :version => '>= 1.6.11'},
|
7
|
-
'route53' => { :require => 'route53', :version => '>= 0.2.1' },
|
8
|
-
'digital_ocean' => { :require => 'digital_ocean', :version => '>= 1.0.0' },
|
9
|
-
}
|
10
|
-
end
|
2
|
+
class Dependency < Struct.new(:name, :version)
|
3
|
+
|
4
|
+
@list = {}
|
11
5
|
|
12
|
-
def
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
rescue LoadError
|
17
|
-
if $!.to_s =~ /Gemfile/
|
18
|
-
Logger.error <<-EOS
|
19
|
-
Dependency missing: #{name}
|
20
|
-
Add this line to your application's Gemfile.
|
6
|
+
def initialize(*args)
|
7
|
+
super
|
8
|
+
self.class.list[name] = self
|
9
|
+
end
|
21
10
|
|
22
|
-
|
11
|
+
class << self
|
12
|
+
attr_accessor :list
|
23
13
|
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
Dependency missing: #{name}
|
29
|
-
To install the gem, issue the following command:
|
14
|
+
def load(key)
|
15
|
+
unless dependency = @list[key]
|
16
|
+
fail "#{key} is not initialized. Run Sunzi::Dependency.new('#{key}', '~> ...')"
|
17
|
+
end
|
30
18
|
|
31
|
-
|
19
|
+
name, version = dependency.name, dependency.version
|
32
20
|
|
33
|
-
|
34
|
-
|
21
|
+
begin
|
22
|
+
gem(name, version)
|
23
|
+
require(name)
|
24
|
+
rescue LoadError
|
25
|
+
base = GemRoot.join('templates/dependency')
|
26
|
+
which = if $!.to_s =~ /Gemfile/
|
27
|
+
'gemfile'
|
28
|
+
else
|
29
|
+
'install'
|
30
|
+
end
|
31
|
+
text = ERB.new(base.join("#{which}.erb").read, nil, '-').result(binding)
|
32
|
+
abort_with text
|
35
33
|
end
|
36
|
-
abort
|
37
34
|
end
|
38
35
|
end
|
39
36
|
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'net/ssh'
|
2
|
+
|
3
|
+
module Sunzi
|
4
|
+
class Endpoint
|
5
|
+
attr_reader :user, :host, :port
|
6
|
+
|
7
|
+
def initialize(input)
|
8
|
+
input.match(/(.*@)?(.*?)(:.*)?$/)
|
9
|
+
# Load ssh config if it exists
|
10
|
+
ssh = Net::SSH::Config.for($2)
|
11
|
+
|
12
|
+
@user = $1 && $1.delete('@') || ssh[:user] || 'root'
|
13
|
+
@host = ssh[:host_name] || $2
|
14
|
+
@port = $3 && $3.delete(':') || ssh[:port] && ssh[:port].to_s || '22'
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
data/lib/sunzi/plugin.rb
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
module Sunzi
|
2
|
+
module Plugin
|
3
|
+
class << self
|
4
|
+
# Find gems that start with "sunzi-*" and require them automatically.
|
5
|
+
# If that gem is a plugin, it will call the register method on load.
|
6
|
+
|
7
|
+
def load
|
8
|
+
plugins = Gem::Specification.find_all.select{|plugin| plugin.name =~ /sunzi-.+/ }
|
9
|
+
plugins.each do |plugin|
|
10
|
+
require plugin.name.gsub('-','/')
|
11
|
+
|
12
|
+
Sunzi.thor.source_paths << Pathname.new(plugin.gem_dir)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
data/sunzi.gemspec
CHANGED
@@ -2,22 +2,22 @@
|
|
2
2
|
|
3
3
|
Gem::Specification.new do |spec|
|
4
4
|
spec.name = 'sunzi'
|
5
|
-
spec.version = '
|
5
|
+
spec.version = '2.0.0' # retrieve this value by: Gem.loaded_specs['sunzi'].version.to_s
|
6
6
|
spec.authors = ['Kenn Ejima']
|
7
7
|
spec.email = ['kenn.ejima@gmail.com']
|
8
|
-
spec.homepage = 'http://github.com/kenn/sunzi'
|
9
8
|
spec.summary = %q{Server provisioning utility for minimalists}
|
10
9
|
spec.description = %q{Server provisioning utility for minimalists}
|
10
|
+
spec.homepage = 'https://github.com/kenn/sunzi'
|
11
11
|
spec.license = 'MIT'
|
12
|
-
|
13
|
-
spec.
|
14
|
-
spec.
|
15
|
-
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
12
|
+
spec.files = `git ls-files -z`.split("\x0").reject {|f| f.match(%r{^(test|spec|features)/}) }
|
13
|
+
spec.bindir = 'exe'
|
14
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
16
15
|
spec.require_paths = ['lib']
|
17
16
|
|
18
|
-
spec.
|
19
|
-
spec.
|
20
|
-
spec.
|
17
|
+
spec.add_dependency 'thor', '~> 0.20'
|
18
|
+
spec.add_dependency 'rainbow', '~> 2.2'
|
19
|
+
spec.add_dependency 'net-ssh', '< 5' # 4.x only supports ruby-2.0
|
20
|
+
spec.add_dependency 'hashugar'
|
21
21
|
spec.add_development_dependency 'rake'
|
22
22
|
spec.add_development_dependency 'minitest'
|
23
23
|
end
|
File without changes
|
File without changes
|
@@ -1,5 +1,4 @@
|
|
1
1
|
#!/bin/bash
|
2
|
-
set -e
|
3
2
|
|
4
3
|
# Load base utility functions like sunzi.mute() and sunzi.install()
|
5
4
|
source recipes/sunzi.sh
|
@@ -8,9 +7,14 @@ source recipes/sunzi.sh
|
|
8
7
|
# Remove if you're not on Debian/Ubuntu.
|
9
8
|
export DEBIAN_FRONTEND=noninteractive
|
10
9
|
|
11
|
-
#
|
12
|
-
|
13
|
-
|
10
|
+
# Import initial SSH keys from Github
|
11
|
+
if [ -f ~/.ssh/authorized_keys ]; then
|
12
|
+
echo 'authorized_keys already exists'
|
13
|
+
else
|
14
|
+
mkdir -p ~/.ssh
|
15
|
+
wget -O - https://github.com/<%= @vars.github_username %>.keys > ~/.ssh/authorized_keys
|
16
|
+
chmod 600 ~/.ssh/authorized_keys
|
17
|
+
fi
|
14
18
|
|
15
19
|
# Update apt catalog and upgrade installed packages
|
16
20
|
sunzi.mute "apt-get update"
|
@@ -26,7 +30,7 @@ if sunzi.install "sysstat"; then
|
|
26
30
|
fi
|
27
31
|
|
28
32
|
# Set RAILS_ENV
|
29
|
-
environment=<%= @
|
33
|
+
environment=<%= @vars.environment %>
|
30
34
|
|
31
35
|
if ! grep -Fq "RAILS_ENV" ~/.bash_profile; then
|
32
36
|
echo 'Setting up RAILS_ENV...'
|
@@ -34,9 +38,11 @@ if ! grep -Fq "RAILS_ENV" ~/.bash_profile; then
|
|
34
38
|
source ~/.bash_profile
|
35
39
|
fi
|
36
40
|
|
37
|
-
# Install
|
41
|
+
# Install RVM using RVM recipe
|
38
42
|
source recipes/rvm.sh
|
39
|
-
|
43
|
+
|
44
|
+
# Install Ruby
|
45
|
+
ruby_version=<%= @vars.ruby_version %>
|
40
46
|
|
41
47
|
if [[ "$(which ruby)" != /usr/local/rvm/rubies/ruby-$ruby_version* ]]; then
|
42
48
|
echo "Installing ruby-$ruby_version"
|
File without changes
|
File without changes
|
File without changes
|
@@ -0,0 +1,27 @@
|
|
1
|
+
---
|
2
|
+
# Dynamic variables here will be accessible from shell scripts using ERB templates.
|
3
|
+
vars:
|
4
|
+
github_username: kenn
|
5
|
+
environment: production
|
6
|
+
ruby_version: 2.5
|
7
|
+
|
8
|
+
# Remote recipes here will be downloaded to compiled/recipes.
|
9
|
+
recipes:
|
10
|
+
rvm: https://raw.githubusercontent.com/kenn/sunzi-recipes/master/ruby/rvm.sh
|
11
|
+
# dotdeb: https://raw.githubusercontent.com/kenn/sunzi-recipes/master/debian/dotdeb-wheezy.sh
|
12
|
+
# backports: https://raw.githubusercontent.com/kenn/sunzi-recipes/master/debian/backports-wheezy.sh
|
13
|
+
# mongodb-10gen: https://raw.githubusercontent.com/kenn/sunzi-recipes/master/debian/mongodb-10gen.sh
|
14
|
+
|
15
|
+
# Files specified here will be copied to compiled/files.
|
16
|
+
# files:
|
17
|
+
# - ~/.ssh/id_rsa.pub
|
18
|
+
|
19
|
+
# Fine tune how Sunzi should work.
|
20
|
+
preferences:
|
21
|
+
# Erase the generated folder on the server after deploy.
|
22
|
+
# Turn on when you are done with testing and ready for production use.
|
23
|
+
erase_remote_folder: true
|
24
|
+
|
25
|
+
# Skip retrieving remote recipes when local copies already exist. This setting helps
|
26
|
+
# iterative deploy testing considerably faster, when you have a lot of remote recipes.
|
27
|
+
cache_remote_recipes: false
|
metadata
CHANGED
@@ -1,45 +1,59 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: sunzi
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 2.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Kenn Ejima
|
8
8
|
autorequire:
|
9
|
-
bindir:
|
9
|
+
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2018-01-14 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: thor
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
16
16
|
requirements:
|
17
|
-
- - "
|
17
|
+
- - "~>"
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: '0'
|
19
|
+
version: '0.20'
|
20
20
|
type: :runtime
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
|
-
- - "
|
24
|
+
- - "~>"
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version: '0'
|
26
|
+
version: '0.20'
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: rainbow
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
30
30
|
requirements:
|
31
|
-
- - "
|
31
|
+
- - "~>"
|
32
32
|
- !ruby/object:Gem::Version
|
33
|
-
version: '
|
33
|
+
version: '2.2'
|
34
34
|
type: :runtime
|
35
35
|
prerelease: false
|
36
36
|
version_requirements: !ruby/object:Gem::Requirement
|
37
37
|
requirements:
|
38
|
-
- - "
|
38
|
+
- - "~>"
|
39
39
|
- !ruby/object:Gem::Version
|
40
|
-
version: '
|
40
|
+
version: '2.2'
|
41
41
|
- !ruby/object:Gem::Dependency
|
42
42
|
name: net-ssh
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "<"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '5'
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "<"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '5'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: hashugar
|
43
57
|
requirement: !ruby/object:Gem::Requirement
|
44
58
|
requirements:
|
45
59
|
- - ">="
|
@@ -94,32 +108,28 @@ files:
|
|
94
108
|
- Gemfile
|
95
109
|
- README.md
|
96
110
|
- Rakefile
|
97
|
-
- bin/
|
111
|
+
- bin/console
|
112
|
+
- bin/setup
|
113
|
+
- exe/sunzi
|
98
114
|
- lib/sunzi.rb
|
115
|
+
- lib/sunzi/actions.rb
|
99
116
|
- lib/sunzi/cli.rb
|
100
|
-
- lib/sunzi/
|
101
|
-
- lib/sunzi/
|
102
|
-
- lib/sunzi/cloud/digital_ocean.rb
|
103
|
-
- lib/sunzi/cloud/linode.rb
|
117
|
+
- lib/sunzi/command.rb
|
118
|
+
- lib/sunzi/core_ext.rb
|
104
119
|
- lib/sunzi/dependency.rb
|
105
|
-
- lib/sunzi/
|
106
|
-
- lib/sunzi/
|
107
|
-
- lib/sunzi/dns/linode.rb
|
108
|
-
- lib/sunzi/dns/route53.rb
|
109
|
-
- lib/sunzi/logger.rb
|
110
|
-
- lib/sunzi/utility.rb
|
111
|
-
- lib/templates/create/.gitignore
|
112
|
-
- lib/templates/create/files/.gitkeep
|
113
|
-
- lib/templates/create/install.sh
|
114
|
-
- lib/templates/create/recipes/sunzi.sh
|
115
|
-
- lib/templates/create/roles/db.sh
|
116
|
-
- lib/templates/create/roles/web.sh
|
117
|
-
- lib/templates/create/sunzi.yml
|
118
|
-
- lib/templates/setup/digital_ocean/digital_ocean.yml
|
119
|
-
- lib/templates/setup/linode/linode.yml
|
120
|
+
- lib/sunzi/endpoint.rb
|
121
|
+
- lib/sunzi/plugin.rb
|
120
122
|
- sunzi.gemspec
|
121
|
-
-
|
122
|
-
|
123
|
+
- templates/create/.gitignore
|
124
|
+
- templates/create/files/.gitkeep
|
125
|
+
- templates/create/install.sh
|
126
|
+
- templates/create/recipes/sunzi.sh
|
127
|
+
- templates/create/roles/db.sh
|
128
|
+
- templates/create/roles/web.sh
|
129
|
+
- templates/create/sunzi.yml
|
130
|
+
- templates/dependency/gemfile.erb
|
131
|
+
- templates/dependency/install.erb
|
132
|
+
homepage: https://github.com/kenn/sunzi
|
123
133
|
licenses:
|
124
134
|
- MIT
|
125
135
|
metadata: {}
|
@@ -139,9 +149,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
139
149
|
version: '0'
|
140
150
|
requirements: []
|
141
151
|
rubyforge_project:
|
142
|
-
rubygems_version: 2.
|
152
|
+
rubygems_version: 2.7.3
|
143
153
|
signing_key:
|
144
154
|
specification_version: 4
|
145
155
|
summary: Server provisioning utility for minimalists
|
146
|
-
test_files:
|
147
|
-
- test/test_cli.rb
|
156
|
+
test_files: []
|
data/lib/sunzi/cloud.rb
DELETED
@@ -1,24 +0,0 @@
|
|
1
|
-
module Sunzi
|
2
|
-
class Cloud
|
3
|
-
include Sunzi::Utility
|
4
|
-
|
5
|
-
def initialize(cli, provider)
|
6
|
-
@subject = case provider
|
7
|
-
when 'linode'
|
8
|
-
Sunzi::Cloud::Linode.new(cli, provider)
|
9
|
-
when 'digital_ocean'
|
10
|
-
Sunzi::Cloud::DigitalOcean.new(cli, provider)
|
11
|
-
else
|
12
|
-
abort_with "Provider #{provider} is not valid!"
|
13
|
-
end
|
14
|
-
end
|
15
|
-
|
16
|
-
def method_missing(sym, *args, &block)
|
17
|
-
@subject.send sym, *args, &block
|
18
|
-
end
|
19
|
-
|
20
|
-
def respond_to?(method)
|
21
|
-
@subject.respond_to?(sym) || super
|
22
|
-
end
|
23
|
-
end
|
24
|
-
end
|
data/lib/sunzi/cloud/base.rb
DELETED
@@ -1,98 +0,0 @@
|
|
1
|
-
Sunzi::Dependency.load('highline')
|
2
|
-
|
3
|
-
module Sunzi
|
4
|
-
class Cloud
|
5
|
-
class Base
|
6
|
-
include Sunzi::Utility
|
7
|
-
|
8
|
-
def initialize(cli, provider)
|
9
|
-
@provider = provider
|
10
|
-
@cli = cli
|
11
|
-
@ui = HighLine.new
|
12
|
-
end
|
13
|
-
|
14
|
-
def setup
|
15
|
-
unless File.exist? provider_config_path
|
16
|
-
@cli.empty_directory "#{@provider}/instances"
|
17
|
-
@cli.template "templates/setup/#{provider_config_path}", provider_config_path
|
18
|
-
exit_with "Now go ahead and edit #{@provider}.yml, then run this command again!"
|
19
|
-
end
|
20
|
-
|
21
|
-
assign_config_and_dns
|
22
|
-
|
23
|
-
if @config['fqdn']['zone'] == 'example.com'
|
24
|
-
abort_with "You must have your own settings in #{@provider}.yml"
|
25
|
-
end
|
26
|
-
|
27
|
-
# Ask environment and hostname
|
28
|
-
@env = ask("environment? (#{@config['environments'].join(' / ')}): ", String) {|q| q.in = @config['environments'] }.to_s
|
29
|
-
@host = ask('hostname? (only the first part of subdomain): ', String).to_s
|
30
|
-
|
31
|
-
abort_with '"label" field in linode.yml is no longer supported. rename it to "name".' if @config['label']
|
32
|
-
@fqdn = @config['fqdn'][@env].gsub(/%{host}/, @host)
|
33
|
-
@name = @config['name'][@env].gsub(/%{host}/, @host)
|
34
|
-
abort_with "#{@name} already exists!" if instance_config_path.exist?
|
35
|
-
|
36
|
-
assign_api
|
37
|
-
@attributes = {}
|
38
|
-
do_setup
|
39
|
-
|
40
|
-
# Save instance info
|
41
|
-
@cli.create_file instance_config_path, YAML.dump(@instance)
|
42
|
-
|
43
|
-
# Register IP to DNS
|
44
|
-
@dns.add(@fqdn, @public_ip) if @dns
|
45
|
-
end
|
46
|
-
|
47
|
-
def teardown
|
48
|
-
names = Dir.glob("#{@provider}/instances/*.yml").map{|i| i.split('/').last.sub('.yml','') }
|
49
|
-
abort_with "No match found with #{@provider}/instances/*.yml" if names.empty?
|
50
|
-
|
51
|
-
names.each{|i| say i }
|
52
|
-
@name = ask("which instance?: ", String) {|q| q.in = names }
|
53
|
-
|
54
|
-
assign_config_and_dns
|
55
|
-
|
56
|
-
@instance = YAML.load(instance_config_path.read)
|
57
|
-
|
58
|
-
# Are you sure?
|
59
|
-
moveon = ask("Are you sure about deleting #{@instance[:fqdn]} permanently? (y/n) ", String) {|q| q.in = ['y','n']}
|
60
|
-
exit unless moveon == 'y'
|
61
|
-
|
62
|
-
# Run Linode / DigitalOcean specific tasks
|
63
|
-
assign_api
|
64
|
-
do_teardown
|
65
|
-
|
66
|
-
# Delete DNS record
|
67
|
-
@dns.delete(@instance[ip_key]) if @dns
|
68
|
-
|
69
|
-
# Remove the instance config file
|
70
|
-
@cli.remove_file instance_config_path
|
71
|
-
|
72
|
-
say 'Done.'
|
73
|
-
end
|
74
|
-
|
75
|
-
def assign_config_and_dns
|
76
|
-
@config = YAML.load(provider_config_path.read)
|
77
|
-
@dns = Sunzi::DNS.new(@config, @provider) if @config['dns']
|
78
|
-
end
|
79
|
-
|
80
|
-
def provider_config_path
|
81
|
-
Pathname.new "#{@provider}/#{@provider}.yml"
|
82
|
-
end
|
83
|
-
|
84
|
-
def instance_config_path
|
85
|
-
Pathname.new "#{@provider}/instances/#{@name}.yml"
|
86
|
-
end
|
87
|
-
|
88
|
-
def ask(question, answer_type, &details)
|
89
|
-
@ui.ask(@ui.color(question, :green, :bold), answer_type, &details)
|
90
|
-
end
|
91
|
-
|
92
|
-
def proceed?
|
93
|
-
moveon = ask("Are you ready to go ahead and create #{@fqdn}? (y/n) ", String) {|q| q.in = ['y','n']}
|
94
|
-
exit unless moveon == 'y'
|
95
|
-
end
|
96
|
-
end
|
97
|
-
end
|
98
|
-
end
|