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 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's `sunzi.yml`, which defines your own dynamic attributes to be used from scripts. Also there's the `remote` folder, which will be transferred to the remote server, that contains recipes and dynamic variables compiled from `sunzi.yml`.
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 `remote` directory to the remote server and extract in `$HOME/sunzi`
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 with no recipes is totally fine, so that you can start small, go big as you get along.
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 # Show command help
59
- $ sunzi compile # Compile Sunzi project
60
- $ sunzi create # Create a new Sunzi project
61
- $ sunzi deploy [user@host:port] # Deploy Sunzi project
62
- $ sunzi setup [linode|ec2] # Setup a new VM on the Cloud services
63
- $ sunzi teardown [linode|ec2] [name] # Teardown an existing VM on the Cloud services
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
- sunzi.yml ---- add custom attributes and remote recipes here
74
- remote/ ---- everything under this folder will be transferred to the remote server
75
- attributes/ ---- compiled attributes from sunzi.yml at deploy (do not edit directly)
76
- ssh_key
77
- recipes/ ---- put commonly used scripts here, referred from install.sh
78
- ssh_key.sh
79
- install.sh ---- main scripts that gets run on the remote server
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, one per attribute. We use filesystem as a sort of key-value storage so that it's easy to use from shell scripts.
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 `remote/recipes` folder in the compile phase.
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 example.com (or user@example.com:2222)", "Deploy sunzi project"
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}/remote"
42
- empty_directory "#{project}/remote/recipes"
43
- template "templates/create/sunzi.yml", "#{project}/sunzi.yml"
44
- template "templates/create/remote/install.sh", "#{project}/remote/install.sh"
45
- template "templates/create/remote/recipes/ssh_key.sh", "#{project}/remote/recipes/ssh_key.sh"
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
- commands = <<-EOS
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
- Open3.popen3(commands) do |stdin, stdout, stderr|
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(stderr) do |terr|
72
- while (line = terr.gets)
73
- print shell.set_color(line, :red, true)
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 print shell.set_color(line, :green, true)
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
- abort_with "You must be in the sunzi folder"
87
- end
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 'remote/attributes'
92
- empty_directory 'remote/recipes'
93
-
94
- # Compile attributes.yml
95
- hash['attributes'].each do |key, value|
96
- File.open("remote/attributes/#{key}", 'w'){|file| file.write(value) }
97
- end
98
- # Compile recipes.yml
99
- hash['recipes'].each do |key, value|
100
- get value, "remote/recipes/#{key}.sh"
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
@@ -1,3 +1,3 @@
1
1
  module Sunzi
2
- VERSION = "0.4.4"
2
+ VERSION = "0.5.0"
3
3
  end
@@ -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
@@ -0,0 +1,5 @@
1
+ # Install Database Server
2
+
3
+ source recipes/mongodb-10gen.sh # MongoDB
4
+ # source recipes/mysql-server.sh 5.5 # MySQL 5.5
5
+ # source recipes/redis-server.sh # Redis
@@ -0,0 +1,4 @@
1
+ # Install Web server
2
+
3
+ source recipes/nginx.sh # Nginx
4
+ # source recipes/apache2.sh # Apache
@@ -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
- # Remote recipes here will be loaded to individual files in remote/recipes.
7
- rvm: https://raw.github.com/kenn/sunzi-recipes/master/ruby/rvm.sh
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.4
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-02 00:00:00.000000000 Z
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: &2160582840 !ruby/object:Gem::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: *2160582840
24
+ version_requirements: *2164772780
25
25
  - !ruby/object:Gem::Dependency
26
26
  name: rainbow
27
- requirement: &2160582300 !ruby/object:Gem::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: *2160582300
35
+ version_requirements: *2164772220
36
36
  - !ruby/object:Gem::Dependency
37
37
  name: rake
38
- requirement: &2160581480 !ruby/object:Gem::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: *2160581480
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/remote/install.sh
70
- - lib/templates/create/remote/recipes/ssh_key.sh
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