sunzi 0.4.4 → 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|