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 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