sunzi 0.4.4 → 0.5.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.
- data/README.md +45 -20
- data/lib/sunzi/cli.rb +53 -35
- data/lib/sunzi/version.rb +1 -1
- data/lib/templates/create/.gitignore +1 -0
- data/lib/templates/create/{remote/install.sh → install.sh} +3 -3
- data/lib/templates/create/recipes/ssh_key.sh +17 -0
- data/lib/templates/create/roles/app.sh +30 -0
- data/lib/templates/create/roles/db.sh +5 -0
- data/lib/templates/create/roles/web.sh +4 -0
- data/lib/templates/create/sunzi.yml +16 -3
- metadata +14 -10
- data/lib/templates/create/remote/recipes/ssh_key.sh +0 -19
data/README.md
CHANGED
@@ -16,6 +16,10 @@ Its design goals are:
|
|
16
16
|
* **Always use the root user.** Think twice before blindly assuming you need a regular user - it doesn't add any security benefit for server provisioning, it just adds extra verbosity for nothing. However, it doesn't mean that you shouldn't create regular users with Sunzi - feel free to write your own recipes.
|
17
17
|
* **Minimum dependencies.** No configuration server required. You don't even need a Ruby runtime on the remote server.
|
18
18
|
|
19
|
+
### What's new:
|
20
|
+
|
21
|
+
* v0.5: Role-based configuration supported. Reworked directory structure. (Incompatible with previous versions)
|
22
|
+
|
19
23
|
Quickstart
|
20
24
|
----------
|
21
25
|
|
@@ -31,7 +35,7 @@ Go into your project directory (if it's a Rails project, `config` would be a goo
|
|
31
35
|
$ sunzi create
|
32
36
|
```
|
33
37
|
|
34
|
-
It generates a `sunzi` folder along with subdirectories and templates. Inside `sunzi`, there
|
38
|
+
It generates a `sunzi` folder along with subdirectories and templates. Inside `sunzi`, there are `sunzi.yml` and `install.sh`. Those two are the most important files that you mainly work on.
|
35
39
|
|
36
40
|
Go into the `sunzi` directory, then run `sunzi deploy`:
|
37
41
|
|
@@ -42,25 +46,25 @@ $ sunzi deploy example.com
|
|
42
46
|
|
43
47
|
Now, what it actually does is:
|
44
48
|
|
45
|
-
1. Compile `sunzi.yml` to generate attributes and retrieve remote recipes
|
49
|
+
1. Compile `sunzi.yml` to generate attributes and retrieve remote recipes, then copy files into the `compiled` directory
|
46
50
|
1. SSH to `example.com` and login as `root`
|
47
|
-
1. Transfer the content of the `
|
51
|
+
1. Transfer the content of the `compiled` directory to the remote server and extract in `$HOME/sunzi`
|
48
52
|
1. Run `install.sh` on the remote server
|
49
53
|
|
50
54
|
As you can see, all you need to do is edit `install.sh` and add some shell commands. That's it.
|
51
55
|
|
52
|
-
A Sunzi project
|
56
|
+
A Sunzi project without any recipes or roles is totally fine, so that you can start small, go big as you get along.
|
53
57
|
|
54
58
|
Commands
|
55
59
|
--------
|
56
60
|
|
57
61
|
```bash
|
58
|
-
$ sunzi
|
59
|
-
$ sunzi compile
|
60
|
-
$ sunzi create
|
61
|
-
$ sunzi deploy [user@host:port]
|
62
|
-
$ sunzi setup [linode|ec2]
|
63
|
-
$ sunzi teardown [linode|ec2] [name]
|
62
|
+
$ sunzi # Show command help
|
63
|
+
$ sunzi compile # Compile Sunzi project
|
64
|
+
$ sunzi create # Create a new Sunzi project
|
65
|
+
$ sunzi deploy [user@host:port] [role] # Deploy Sunzi project
|
66
|
+
$ sunzi setup [linode|ec2] # Setup a new VM on the Cloud services
|
67
|
+
$ sunzi teardown [linode|ec2] [name] # Teardown an existing VM on the Cloud services
|
64
68
|
```
|
65
69
|
|
66
70
|
Directory structure
|
@@ -68,21 +72,25 @@ Directory structure
|
|
68
72
|
|
69
73
|
Here's the directory structure that `sunzi create` automatically generates:
|
70
74
|
|
71
|
-
```
|
75
|
+
```bash
|
72
76
|
sunzi/
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
77
|
+
install.sh # main script
|
78
|
+
sunzi.yml # add custom attributes and remote recipes here
|
79
|
+
|
80
|
+
recipes/ # put commonly used scripts here, referred from install.sh
|
81
|
+
ssh_key.sh
|
82
|
+
roles/ # when role is specified, scripts here will be concatenated
|
83
|
+
app.sh # to install.sh in the compile phase
|
84
|
+
db.sh
|
85
|
+
web.sh
|
86
|
+
compiled/ # everything under this folder will be transferred to the
|
87
|
+
# remote server (do not edit directly)
|
80
88
|
```
|
81
89
|
|
82
90
|
How do you pass dynamic values to a recipe?
|
83
91
|
-------------------------------------------
|
84
92
|
|
85
|
-
In the compile phase, attributes defined in `sunzi.yml` are split into multiple files
|
93
|
+
In the compile phase, attributes defined in `sunzi.yml` are split into multiple files in `compiled/attributes`, one per attribute. We use filesystem as a sort of key-value storage so that it's easy to use from shell scripts.
|
86
94
|
|
87
95
|
The convention for argument passing to a recipe is to use `$1`, `$2`, etc. and put a comment line for each argument.
|
88
96
|
|
@@ -119,7 +127,7 @@ Goodbye Chef, Hello Sunzi!
|
|
119
127
|
Remote Recipes
|
120
128
|
--------------
|
121
129
|
|
122
|
-
Recipes can be retrieved remotely via HTTP. Put a URL in the recipes section of `sunzi.yml`, and Sunzi will automatically load the content and put it into the `
|
130
|
+
Recipes can be retrieved remotely via HTTP. Put a URL in the recipes section of `sunzi.yml`, and Sunzi will automatically load the content and put it into the `compiled/recipes` folder in the compile phase.
|
123
131
|
|
124
132
|
For instance, if you have the following line in `sunzi.yml`,
|
125
133
|
|
@@ -132,6 +140,23 @@ recipes:
|
|
132
140
|
|
133
141
|
You may find sample recipes in this repository useful: https://github.com/kenn/sunzi-recipes
|
134
142
|
|
143
|
+
Role-based configuration
|
144
|
+
------------------------
|
145
|
+
|
146
|
+
You probably have different configurations between **web servers** and **database servers**.
|
147
|
+
|
148
|
+
No problem - how Sunzi handles role-based configuration is refreshingly simple.
|
149
|
+
|
150
|
+
Shell scripts under the `roles` directory, such as `web.sh` or `db.sh`, are automatically recognized as a role. The role script will be appended to `install.sh` at deploy, so you should put common configurations in `install.sh` and role specific procedures in the role script.
|
151
|
+
|
152
|
+
For instance, when you set up a new web server, deploy with a role name:
|
153
|
+
|
154
|
+
```bash
|
155
|
+
sunzi deploy example.com web
|
156
|
+
```
|
157
|
+
|
158
|
+
It is equivalent to running `install.sh`, followed by `web.sh`.
|
159
|
+
|
135
160
|
Cloud Support
|
136
161
|
-------------
|
137
162
|
|
data/lib/sunzi/cli.rb
CHANGED
@@ -9,14 +9,14 @@ module Sunzi
|
|
9
9
|
do_create(project)
|
10
10
|
end
|
11
11
|
|
12
|
-
desc "deploy
|
13
|
-
def deploy(target)
|
14
|
-
do_deploy(target)
|
12
|
+
desc "deploy [user@host:port] [role]", "Deploy sunzi project"
|
13
|
+
def deploy(target, role = nil)
|
14
|
+
do_deploy(target, role)
|
15
15
|
end
|
16
16
|
|
17
17
|
desc "compile", "Compile sunzi project"
|
18
|
-
def compile
|
19
|
-
do_compile
|
18
|
+
def compile(role = nil)
|
19
|
+
do_compile(role)
|
20
20
|
end
|
21
21
|
|
22
22
|
desc "setup [linode|ec2]", "Setup a new VM"
|
@@ -38,66 +38,84 @@ module Sunzi
|
|
38
38
|
|
39
39
|
def do_create(project)
|
40
40
|
empty_directory project
|
41
|
-
empty_directory "#{project}/
|
42
|
-
empty_directory "#{project}/
|
43
|
-
template "templates/create
|
44
|
-
template "templates/create/
|
45
|
-
template "templates/create/
|
41
|
+
empty_directory "#{project}/recipes"
|
42
|
+
empty_directory "#{project}/roles"
|
43
|
+
# template "templates/create/.gitignore", "#{project}/.gitignore"
|
44
|
+
template "templates/create/sunzi.yml", "#{project}/sunzi.yml"
|
45
|
+
template "templates/create/install.sh", "#{project}/install.sh"
|
46
|
+
template "templates/create/recipes/ssh_key.sh", "#{project}/recipes/ssh_key.sh"
|
47
|
+
template "templates/create/roles/app.sh", "#{project}/roles/app.sh"
|
48
|
+
template "templates/create/roles/db.sh", "#{project}/roles/db.sh"
|
49
|
+
template "templates/create/roles/web.sh", "#{project}/roles/web.sh"
|
46
50
|
end
|
47
51
|
|
48
|
-
def do_deploy(target)
|
52
|
+
def do_deploy(target, role)
|
49
53
|
user, host, port = parse_target(target)
|
50
54
|
endpoint = "#{user}@#{host}"
|
51
55
|
|
52
56
|
# compile attributes and recipes
|
53
|
-
compile
|
57
|
+
compile(role)
|
54
58
|
|
55
59
|
# The host key might change when we instantiate a new VM, so
|
56
60
|
# we remove (-R) the old host key from known_hosts.
|
57
61
|
`ssh-keygen -R #{host} 2> /dev/null`
|
58
62
|
|
59
|
-
|
60
|
-
cd remote
|
61
|
-
tar cz . | ssh -o 'StrictHostKeyChecking no' #{endpoint} -p #{port} '
|
63
|
+
remote_commands = <<-EOS
|
62
64
|
rm -rf ~/sunzi &&
|
63
65
|
mkdir ~/sunzi &&
|
64
66
|
cd ~/sunzi &&
|
65
67
|
tar xz &&
|
66
|
-
bash install.sh
|
68
|
+
bash install.sh
|
67
69
|
EOS
|
68
70
|
|
69
|
-
|
71
|
+
local_commands = <<-EOS
|
72
|
+
cd compiled
|
73
|
+
tar cz . | ssh -o 'StrictHostKeyChecking no' #{endpoint} -p #{port} '#{remote_commands}'
|
74
|
+
EOS
|
75
|
+
|
76
|
+
Open3.popen3(local_commands) do |stdin, stdout, stderr|
|
70
77
|
stdin.close
|
71
|
-
t = Thread.new
|
72
|
-
while (line =
|
73
|
-
print
|
78
|
+
t = Thread.new do
|
79
|
+
while (line = stderr.gets)
|
80
|
+
print line.color(:red).bright
|
74
81
|
end
|
75
82
|
end
|
76
83
|
while (line = stdout.gets)
|
77
|
-
print
|
84
|
+
print line.color(:green).bright
|
78
85
|
end
|
79
86
|
t.join
|
80
87
|
end
|
81
88
|
end
|
82
89
|
|
83
|
-
def do_compile
|
90
|
+
def do_compile(role)
|
84
91
|
# Check if you're in the sunzi directory
|
85
|
-
unless File.exists?('sunzi.yml')
|
86
|
-
|
87
|
-
|
92
|
+
abort_with "You must be in the sunzi folder" unless File.exists?('sunzi.yml')
|
93
|
+
# Check if role exists
|
94
|
+
abort_with "#{role} doesn't exist!" if role and !File.exists?("roles/#{role}.sh")
|
88
95
|
|
89
96
|
# Load sunzi.yml
|
90
97
|
hash = YAML.load(File.read('sunzi.yml'))
|
91
|
-
empty_directory '
|
92
|
-
empty_directory '
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
98
|
+
empty_directory 'compiled'
|
99
|
+
empty_directory 'compiled/attributes'
|
100
|
+
empty_directory 'compiled/recipes'
|
101
|
+
empty_directory 'compiled/files'
|
102
|
+
|
103
|
+
# Break down attributes into individual files
|
104
|
+
hash['attributes'].each {|key, value| create_file "compiled/attributes/#{key}", value }
|
105
|
+
|
106
|
+
# Retrieve remote recipes via HTTP
|
107
|
+
hash['recipes'].each {|key, value| get value, "compiled/recipes/#{key}.sh" }
|
108
|
+
|
109
|
+
# Copy local files
|
110
|
+
Dir['recipes/*'].each {|file| copy_file File.expand_path(file), "compiled/recipes/#{File.basename(file)}" }
|
111
|
+
Dir['roles/*'].each {|file| copy_file File.expand_path(file), "compiled/roles/#{File.basename(file)}" }
|
112
|
+
hash['files'].each {|file| copy_file File.expand_path(file), "compiled/files/#{File.basename(file)}" }
|
113
|
+
|
114
|
+
# Build install.sh
|
115
|
+
if role
|
116
|
+
create_file 'compiled/install.sh', File.binread("install.sh") << "\n" << File.binread("roles/#{role}.sh")
|
117
|
+
else
|
118
|
+
copy_file File.expand_path('install.sh'), 'compiled/install.sh'
|
101
119
|
end
|
102
120
|
end
|
103
121
|
|
data/lib/sunzi/version.rb
CHANGED
@@ -0,0 +1 @@
|
|
1
|
+
/compiled
|
@@ -6,9 +6,9 @@ export DEBIAN_FRONTEND=noninteractive
|
|
6
6
|
# SSH key
|
7
7
|
source recipes/ssh_key.sh $(cat attributes/ssh_key)
|
8
8
|
|
9
|
+
# Add Dotdeb repository
|
10
|
+
source recipes/dotdeb.sh
|
11
|
+
|
9
12
|
# Update apt catalog
|
10
13
|
aptitude update
|
11
14
|
aptitude -y safe-upgrade
|
12
|
-
|
13
|
-
# Install RVM - rvm.sh will be retrieved from Github in the compile phase
|
14
|
-
source recipes/rvm.sh
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# SSH key
|
2
|
+
# $1: SSH key filename
|
3
|
+
|
4
|
+
if [ -f ~/.ssh/authorized_keys ]; then
|
5
|
+
echo 'authorized_keys already created'
|
6
|
+
else
|
7
|
+
if [ -f "files/$1" ]; then
|
8
|
+
mkdir -p ~/.ssh
|
9
|
+
cat "files/$1" > ~/.ssh/authorized_keys
|
10
|
+
rm "files/$1"
|
11
|
+
chmod 700 ~/.ssh
|
12
|
+
chmod 600 ~/.ssh/authorized_keys
|
13
|
+
else
|
14
|
+
echo "The public key file \"$1\" is not found! Look into files section in sunzi.yml."
|
15
|
+
exit 1
|
16
|
+
fi
|
17
|
+
fi
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# Install Application Server
|
2
|
+
|
3
|
+
# Required attributes: env, ruby_version
|
4
|
+
export ENVIRONMENT=$(cat attributes/environment)
|
5
|
+
export RUBY_VERSION=$(cat attributes/ruby_version)
|
6
|
+
|
7
|
+
# Install RVM
|
8
|
+
source recipes/rvm.sh
|
9
|
+
|
10
|
+
# Set RAILS_ENV
|
11
|
+
if grep -Fq "RAILS_ENV" ~/.bash_profile; then
|
12
|
+
echo 'RAILS_ENV entry already exists'
|
13
|
+
else
|
14
|
+
echo "export RAILS_ENV=$ENVIRONMENT" >> ~/.bash_profile
|
15
|
+
source ~/.bash_profile
|
16
|
+
fi
|
17
|
+
|
18
|
+
# Install Ruby
|
19
|
+
if [[ "$(which ruby)" == /usr/local/rvm/rubies/ruby-$RUBY_VERSION* ]]; then
|
20
|
+
echo 'ruby-$RUBY_VERSION already installed'
|
21
|
+
else
|
22
|
+
aptitude -y install curl build-essential libssl-dev libreadline6-dev
|
23
|
+
rvm install $RUBY_VERSION
|
24
|
+
rvm $RUBY_VERSION --default
|
25
|
+
echo 'gem: --no-ri --no-rdoc' > ~/.gemrc
|
26
|
+
|
27
|
+
# Install RubyGems
|
28
|
+
gem update --system
|
29
|
+
gem install bundler
|
30
|
+
fi
|
@@ -1,7 +1,20 @@
|
|
1
1
|
---
|
2
|
+
# Dynamic variables here will be compiled to individual files in compiled/attributes.
|
2
3
|
attributes:
|
3
|
-
# Dynamic variables here will be compiled to individual files in remote/attributes.
|
4
4
|
ssh_key: id_rsa.pub
|
5
|
+
environment: development
|
6
|
+
ruby_version: 1.9.3
|
7
|
+
|
8
|
+
# Remote recipes here will be downloaded to compiled/recipes.
|
5
9
|
recipes:
|
6
|
-
|
7
|
-
|
10
|
+
rvm: https://raw.github.com/kenn/sunzi-recipes/master/ruby/rvm.sh
|
11
|
+
dotdeb: https://raw.github.com/kenn/sunzi-recipes/master/debian/dotdeb.sh
|
12
|
+
# nginx: https://raw.github.com/kenn/sunzi-recipes/master/debian/nginx.sh
|
13
|
+
# apache2: https://raw.github.com/kenn/sunzi-recipes/master/debian/apache2.sh
|
14
|
+
# mongodb-10gen: https://raw.github.com/kenn/sunzi-recipes/master/debian/mongodb-10gen.sh
|
15
|
+
# mysql-server: https://raw.github.com/kenn/sunzi-recipes/master/debian/mysql-server.sh
|
16
|
+
# redis-server: https://raw.github.com/kenn/sunzi-recipes/master/debian/redis-server.sh
|
17
|
+
|
18
|
+
# Files specified here will be copied to compiled/.
|
19
|
+
files:
|
20
|
+
- ~/.ssh/id_rsa.pub
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: sunzi
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.5.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,11 +9,11 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2012-03-
|
12
|
+
date: 2012-03-03 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: thor
|
16
|
-
requirement: &
|
16
|
+
requirement: &2164772780 !ruby/object:Gem::Requirement
|
17
17
|
none: false
|
18
18
|
requirements:
|
19
19
|
- - ! '>='
|
@@ -21,10 +21,10 @@ dependencies:
|
|
21
21
|
version: '0'
|
22
22
|
type: :runtime
|
23
23
|
prerelease: false
|
24
|
-
version_requirements: *
|
24
|
+
version_requirements: *2164772780
|
25
25
|
- !ruby/object:Gem::Dependency
|
26
26
|
name: rainbow
|
27
|
-
requirement: &
|
27
|
+
requirement: &2164772220 !ruby/object:Gem::Requirement
|
28
28
|
none: false
|
29
29
|
requirements:
|
30
30
|
- - ! '>='
|
@@ -32,10 +32,10 @@ dependencies:
|
|
32
32
|
version: '0'
|
33
33
|
type: :runtime
|
34
34
|
prerelease: false
|
35
|
-
version_requirements: *
|
35
|
+
version_requirements: *2164772220
|
36
36
|
- !ruby/object:Gem::Dependency
|
37
37
|
name: rake
|
38
|
-
requirement: &
|
38
|
+
requirement: &2164771480 !ruby/object:Gem::Requirement
|
39
39
|
none: false
|
40
40
|
requirements:
|
41
41
|
- - ! '>='
|
@@ -43,7 +43,7 @@ dependencies:
|
|
43
43
|
version: '0'
|
44
44
|
type: :development
|
45
45
|
prerelease: false
|
46
|
-
version_requirements: *
|
46
|
+
version_requirements: *2164771480
|
47
47
|
description: Server provisioning utility for minimalists
|
48
48
|
email:
|
49
49
|
- kenn.ejima@gmail.com
|
@@ -66,8 +66,12 @@ files:
|
|
66
66
|
- lib/sunzi/logger.rb
|
67
67
|
- lib/sunzi/utility.rb
|
68
68
|
- lib/sunzi/version.rb
|
69
|
-
- lib/templates/create
|
70
|
-
- lib/templates/create/
|
69
|
+
- lib/templates/create/.gitignore
|
70
|
+
- lib/templates/create/install.sh
|
71
|
+
- lib/templates/create/recipes/ssh_key.sh
|
72
|
+
- lib/templates/create/roles/app.sh
|
73
|
+
- lib/templates/create/roles/db.sh
|
74
|
+
- lib/templates/create/roles/web.sh
|
71
75
|
- lib/templates/create/sunzi.yml
|
72
76
|
- lib/templates/setup/linode/linode.yml
|
73
77
|
- sunzi.gemspec
|
@@ -1,19 +0,0 @@
|
|
1
|
-
# SSH key
|
2
|
-
# $1: SSH key filename
|
3
|
-
|
4
|
-
if [ -f ~/.ssh/authorized_keys ]; then
|
5
|
-
echo 'authorized_keys already created'
|
6
|
-
else
|
7
|
-
if [ -f "$1" ]; then
|
8
|
-
mkdir -p ~/.ssh
|
9
|
-
cat $1 > ~/.ssh/authorized_keys
|
10
|
-
rm $1
|
11
|
-
chmod 700 ~/.ssh
|
12
|
-
chmod 600 ~/.ssh/authorized_keys
|
13
|
-
else
|
14
|
-
echo "The public key file is not found! Try the following command:"
|
15
|
-
echo "cp ~/.ssh/$1 remote"
|
16
|
-
echo "If the file name found in ~/.ssh is different from \"$1\", edit sunzi.yml as appropriate."
|
17
|
-
exit 1
|
18
|
-
fi
|
19
|
-
fi
|