sunzi 0.3.0 → 0.4.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
@@ -27,7 +27,7 @@ Go to your project directory, then:
27
27
 
28
28
  $ sunzi create
29
29
 
30
- It generates a `sunzi` folder along with subdirectories and templates. Inside `sunzi`, there's `attributes.yml`, which defines dynamic attributes to be used from recipes. Also there's the `remote` folder, which will be transferred to the remote server, that contains recipes and dynamic variables compiled from `attributes.yml`.
30
+ It generates a `sunzi` folder along with subdirectories and templates. Inside `sunzi`, there's `sunzi.yml`, which defines dynamic attributes to be used from recipes. Also there's the `remote` folder, which will be transferred to the remote server, that contains recipes and dynamic variables compiled from `sunzi.yml`.
31
31
 
32
32
  Go into the `sunzi` directory, then run the `sunzi deploy`:
33
33
 
@@ -38,7 +38,7 @@ Now, what it actually does is:
38
38
 
39
39
  1. SSH to `example.com` and login as `root`
40
40
  1. Transfer the content of the `remote` directory to the remote server and extract in `$HOME/sunzi`
41
- 1. Run `install.sh` in the remote server
41
+ 1. Run `install.sh` on the remote server
42
42
 
43
43
  As you can see, what you need to do is edit `install.sh` and add some shell commands. That's it.
44
44
 
@@ -61,13 +61,13 @@ sunzi/
61
61
  How do you pass dynamic values to a recipe?
62
62
  -------------------------------------------
63
63
 
64
- In the compile phase, `attributes.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.
64
+ 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.
65
65
 
66
66
  The convention for argument passing to a recipe is to use `$1`, `$2`, etc. and put a comment line for each argument.
67
67
 
68
68
  For instance, given a recipe `greeting.sh`:
69
69
 
70
- ```
70
+ ```bash
71
71
  # Greeting
72
72
  # $1: Name for goodbye
73
73
  # $2: Name for hello
@@ -77,14 +77,14 @@ echo "Goodbye $1, Hello $2!"
77
77
 
78
78
  With `attributes.yml`:
79
79
 
80
- ```
80
+ ```yaml
81
81
  goodbye: Chef
82
82
  hello: Sunzi
83
83
  ```
84
84
 
85
85
  Then, include the recipe in `install.sh`:
86
86
 
87
- ```
87
+ ```bash
88
88
  source recipes/greeting.sh $(cat attributes/goodbye) $(cat attributes/hello)
89
89
  ```
90
90
 
@@ -97,16 +97,26 @@ Goodbye Chef, Hello Sunzi!
97
97
  Remote Recipes
98
98
  --------------
99
99
 
100
- Recipes can be retrieved remotely via HTTP. Put a URL in `recipes.yml`, and Sunzi automatically loads the content and put it into the `remote/recipes` folder.
100
+ 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.
101
101
 
102
- For instance, if you have the following line in `recipes.yml`,
102
+ For instance, if you have the following line in `sunzi.yml`,
103
103
 
104
- ```
105
- rvm: https://raw.github.com/kenn/sunzi-recipes/master/ruby/rvm.sh
104
+ ```yaml
105
+ recipes:
106
+ rvm: https://raw.github.com/kenn/sunzi-recipes/master/ruby/rvm.sh
106
107
  ```
107
108
 
108
109
  `rvm.sh` will be available and you can refer to that recipe by `source recipes/rvm.sh`.
109
110
 
111
+ Cloud Support
112
+ -------------
113
+
114
+ You can setup a new VM / teardown an existing VM interactively. Use `sunzi setup` and `sunzi teardown` for that.
115
+
116
+ The following screenshot says it all.
117
+
118
+ ![Sunzi for Linode](http://farm8.staticflickr.com/7210/6783789868_ab89010d5c.jpg)
119
+
110
120
  Vagrant
111
121
  -------
112
122
 
@@ -124,7 +134,7 @@ end
124
134
 
125
135
  with `chpasswd.sh`:
126
136
 
127
- ```
137
+ ```bash
128
138
  #!/bin/bash
129
139
 
130
140
  sudo echo 'root:vagrant' | /usr/sbin/chpasswd
data/bin/sunzi CHANGED
@@ -1,3 +1,4 @@
1
1
  #!/usr/bin/env ruby
2
+ Signal.trap(:INT) { abort "\nAborting." }
2
3
  require File.expand_path('../../lib/sunzi',__FILE__)
3
4
  Sunzi::Cli.start
@@ -1,7 +1,16 @@
1
1
  LIB_PATH = File.join(File.dirname(__FILE__), 'sunzi')
2
2
 
3
+ require 'thor'
4
+ require 'yaml'
5
+
3
6
  module Sunzi
4
- autoload :Base, File.join(LIB_PATH, 'base')
5
7
  autoload :Cli, File.join(LIB_PATH, 'cli')
8
+ autoload :Dependency, File.join(LIB_PATH, 'dependency')
6
9
  autoload :Version, File.join(LIB_PATH, 'version')
10
+
11
+ module Cloud
12
+ autoload :Base, File.join(LIB_PATH, 'cloud', 'base')
13
+ autoload :Linode, File.join(LIB_PATH, 'cloud', 'linode')
14
+ autoload :EC2, File.join(LIB_PATH, 'cloud', 'ec2')
15
+ end
7
16
  end
@@ -1,6 +1,3 @@
1
- require 'thor'
2
- require 'yaml'
3
- require 'fileutils'
4
1
  require 'open3'
5
2
 
6
3
  module Sunzi
@@ -23,9 +20,9 @@ module Sunzi
23
20
  empty_directory project
24
21
  empty_directory "#{project}/remote"
25
22
  empty_directory "#{project}/remote/recipes"
26
- template "templates/sunzi.yml", "#{project}/sunzi.yml"
27
- template "templates/remote/install.sh", "#{project}/remote/install.sh"
28
- template "templates/remote/recipes/ssh_key.sh", "#{project}/remote/recipes/ssh_key.sh"
23
+ template "templates/create/sunzi.yml", "#{project}/sunzi.yml"
24
+ template "templates/create/remote/install.sh", "#{project}/remote/install.sh"
25
+ template "templates/create/remote/recipes/ssh_key.sh", "#{project}/remote/recipes/ssh_key.sh"
29
26
  end
30
27
 
31
28
  desc "deploy example.com (or user@example.com:2222)", "Deploy sunzi project"
@@ -87,6 +84,16 @@ module Sunzi
87
84
  end
88
85
  end
89
86
 
87
+ desc "setup [linode|ec2]", "Setup a new VM"
88
+ def setup(target)
89
+ Cloud::Base.choose(target).setup
90
+ end
91
+
92
+ desc "teardown [linode|ec2] [name]", "Teardown an existing VM"
93
+ def teardown(target, name)
94
+ Cloud::Base.choose(target).teardown(name)
95
+ end
96
+
90
97
  no_tasks do
91
98
  def parse_target(target)
92
99
  target.match(/(.*@)?(.*?)(:.*)?$/)
@@ -0,0 +1,38 @@
1
+ Sunzi::Dependency.load('highline')
2
+
3
+ module Sunzi
4
+ module Cloud
5
+ class Base < Thor
6
+ include Thor::Actions
7
+
8
+ class << self
9
+ def source_root
10
+ File.expand_path('../../../',__FILE__)
11
+ end
12
+
13
+ def choose(target)
14
+ case target
15
+ when 'linode'
16
+ Cloud::Linode.new
17
+ when 'ec2'
18
+ Cloud::EC2.new
19
+ else
20
+ say shell.set_color("#{target} is not valid!", :red, true)
21
+ abort
22
+ end
23
+ end
24
+ end
25
+
26
+ def initialize(*args)
27
+ @ui = HighLine.new
28
+ super
29
+ end
30
+
31
+ no_tasks do
32
+ def ask(question, answer_type, &details)
33
+ @ui.ask(@ui.color(question, :green, :bold), answer_type, &details)
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,15 @@
1
+ module Sunzi
2
+ module Cloud
3
+ class EC2 < Base
4
+ no_tasks do
5
+ def setup
6
+ say shell.set_color('EC2 is not implemented yet!', :red, true)
7
+ end
8
+
9
+ def teardown(target)
10
+ say shell.set_color('EC2 is not implemented yet!', :red, true)
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,248 @@
1
+ Sunzi::Dependency.load('linode')
2
+
3
+ # Workaround the bug: https://github.com/rick/linode/issues/12
4
+ YAML::ENGINE.yamler = 'syck'
5
+
6
+ module Sunzi
7
+ module Cloud
8
+ class Linode < Base
9
+ no_tasks do
10
+
11
+ def setup
12
+ # Only run for the first time
13
+ unless File.exist? 'linode/linode.yml'
14
+ empty_directory 'linode'
15
+ empty_directory 'linode/instances'
16
+ template 'templates/setup/linode/linode.yml', 'linode/linode.yml'
17
+ say shell.set_color('Now go ahead and edit linode.yml, then run this command again!', :green, true)
18
+ abort
19
+ end
20
+
21
+ @config = YAML.load(File.read('linode/linode.yml'))
22
+
23
+ if @config['fqdn']['zone'] == 'example.com'
24
+ say shell.set_color('You must have your own settings in linode.yml', :red, true)
25
+ abort
26
+ end
27
+
28
+ # When route53 is specified for DNS, check if it's properly configured and if not, fail earlier.
29
+ setup_route53 if @config['dns'] == 'route53'
30
+
31
+ @ui = HighLine.new
32
+
33
+ @sshkey = File.read(File.expand_path(@config['root_sshkey_path'])).chomp
34
+ if @sshkey.match(/\n/)
35
+ say shell.set_color("RootSSHKey #{@sshkey.inspect} must not be multi-line! Check inside \"#{@config['root_sshkey_path']}\"", :red, true)
36
+ abort
37
+ end
38
+
39
+ # Ask environment and hostname
40
+ @env = ask("environment? (#{@config['environments'].join(' / ')}): ", String) {|q| q.in = @config['environments'] }.to_s
41
+ @host = ask('hostname? (only the last part of subdomain): ', String).to_s
42
+
43
+ @fqdn = @config['fqdn'][@env].gsub(/%{host}/, @host)
44
+ @label = @config['label'][@env].gsub(/%{host}/, @host)
45
+ @group = @config['group'][@env]
46
+ @api = ::Linode.new(:api_key => @config['api_key'])
47
+
48
+ # Choose a plan
49
+ result = @api.avail.linodeplans
50
+ result.each{|i| say "#{i.planid}: #{i.ram}MB, $#{i.price}" }
51
+ @planid = ask('which plan?: ', Integer) {|q| q.in = result.map(&:planid); q.default = result.first.planid }
52
+ @plan_label = result.find{|i| i.planid == @planid }.label
53
+
54
+ # Choose a datacenter
55
+ result = @api.avail.datacenters
56
+ result.each{|i| say "#{i.datacenterid}: #{i.location}" }
57
+ @datacenterid = ask('which datacenter?: ', Integer) {|q| q.in = result.map(&:datacenterid); q.default = result.first.datacenterid }
58
+ @datacenter_location = result.find{|i| i.datacenterid == @datacenterid }.location
59
+
60
+ # Choose a distribution
61
+ result = @api.avail.distributions
62
+ if @config['distributions_filter']
63
+ result = result.select{|i| i.label.match Regexp.new(@config['distributions_filter'], Regexp::IGNORECASE) }
64
+ end
65
+ result.each{|i| say "#{i.distributionid}: #{i.label}" }
66
+ @distributionid = ask('which distribution?: ', Integer) {|q| q.in = result.map(&:distributionid); q.default = result.first.distributionid }
67
+ @distribution_label = result.find{|i| i.distributionid == @distributionid }.label
68
+
69
+ # Choose a kernel
70
+ result = @api.avail.kernels
71
+ if @config['kernels_filter']
72
+ result = result.select{|i| i.label.match Regexp.new(@config['kernels_filter'], Regexp::IGNORECASE) }
73
+ end
74
+ result.each{|i| say "#{i.kernelid}: #{i.label}" }
75
+ @kernelid = ask('which kernel?: ', Integer) {|q| q.in = result.map(&:kernelid); q.default = result.first.kernelid }
76
+ @kernel_label = result.find{|i| i.kernelid == @kernelid }.label
77
+
78
+ # Choose swap size
79
+ @swap_size = ask('swap size in MB? (default: 256MB): ', Integer) { |q| q.default = 256 }
80
+
81
+ # Go ahead?
82
+ moveon = ask("Are you sure to go ahead and create #{@fqdn}? (y/n) ", String) {|q| q.in = ['y','n']}
83
+ exit unless moveon == 'y'
84
+
85
+ # Create
86
+ say "creating a new linode..."
87
+ result = @api.linode.create(:DatacenterID => @datacenterid, :PlanID => @planid, :PaymentTerm => @config['payment_term'])
88
+ @linodeid = result.linodeid
89
+ say "created a new instance: linodeid = #{@linodeid}"
90
+
91
+ result = @api.linode.list.select{|i| i.linodeid == @linodeid }.first
92
+ @totalhd = result.totalhd
93
+
94
+ # Update settings
95
+ say "Updating settings..."
96
+ result = @api.linode.update(
97
+ :LinodeID => @linodeid,
98
+ :Label => @label,
99
+ :lpm_displayGroup => @group,
100
+ # :Alert_cpu_threshold => 90,
101
+ # :Alert_diskio_threshold => 1000,
102
+ # :Alert_bwin_threshold => 5,
103
+ # :Alert_bwout_threshold => 5,
104
+ # :Alert_bwquota_threshold => 80,
105
+ )
106
+
107
+ # Create a root disk
108
+ say "Creating a root disk..."
109
+ result = @api.linode.disk.createfromdistribution(
110
+ :LinodeID => @linodeid,
111
+ :DistributionID => @distributionid,
112
+ :Label => "#{@distribution_label} Image",
113
+ :Size => @totalhd - @swap_size,
114
+ :rootPass => @config['root_pass'],
115
+ :rootSSHKey => @sshkey
116
+ )
117
+ @root_diskid = result.diskid
118
+
119
+ # Create a swap disk
120
+ say "Creating a swap disk..."
121
+ result = @api.linode.disk.create(
122
+ :LinodeID => @linodeid,
123
+ :Label => "#{@swap_size}MB Swap Image",
124
+ :Type => 'swap',
125
+ :Size => @swap_size
126
+ )
127
+ @swap_diskid = result.diskid
128
+
129
+ # Create a config profiile
130
+ say "Creating a config profile..."
131
+ result = @api.linode.config.create(
132
+ :LinodeID => @linodeid,
133
+ :KernelID => @kernelid,
134
+ :Label => "#{@distribution_label} Profile",
135
+ :DiskList => [ @root_diskid, @swap_diskid ].join(',')
136
+ )
137
+ @config_id = result.configid
138
+
139
+ # Add a private IP
140
+ say "Adding a private IP..."
141
+ result = @api.linode.ip.list(:LinodeID => @linodeid)
142
+ @public_ip = result.first.ipaddress
143
+ result = @api.linode.ip.addprivate(:LinodeID => @linodeid)
144
+ result = @api.linode.ip.list(:LinodeID => @linodeid).find{|i| i.ispublic == 0 }
145
+ @private_ip = result.ipaddress
146
+
147
+ # Register IP to DNS
148
+ case @config['dns']
149
+ when 'linode'
150
+ # Set the public IP to Linode DNS Manager
151
+ say "Setting the public IP to Linode DNS Manager..."
152
+ @domainid = @api.domain.list.find{|i| i.domain == @config['fqdn']['zone'] }.domainid
153
+ @api.domain.resource.create(:DomainID => @domainid, :Type => 'A', :Name => @fqdn, :Target => @public_ip)
154
+ when 'route53'
155
+ # Set the public IP to AWS Route 53
156
+ say "Setting the public IP to AWS Route 53..."
157
+ Route53::DNSRecord.new(@fqdn, "A", "300", [@public_ip], @route53_zone).create
158
+ end
159
+
160
+ # Boot
161
+ say shell.set_color("Done. Booting...", :green, true)
162
+ @api.linode.boot(:LinodeID => @linodeid)
163
+
164
+ hash = {
165
+ :linode_id => @linodeid,
166
+ :env => @env,
167
+ :host => @host,
168
+ :fqdn => @fqdn,
169
+ :label => @label,
170
+ :group => @group,
171
+ :plan_id => @planid,
172
+ :datacenter_id => @datacenterid,
173
+ :datacenter_location => @datacenter_location,
174
+ :distribution_id => @distributionid,
175
+ :distribution_label => @distribution_label,
176
+ :kernel_id => @kernelid,
177
+ :kernel_label => @kernel_label,
178
+ :swap_size => @swap_size,
179
+ :totalhd => @totalhd,
180
+ :root_diskid => @root_diskid,
181
+ :swap_diskid => @swap_diskid,
182
+ :config_id => @config_id,
183
+ :public_ip => @public_ip,
184
+ :private_ip => @private_ip,
185
+ }
186
+
187
+ File.open("linode/instances/#{@label}.yml",'w') do |file|
188
+ file.write YAML.dump(hash)
189
+ end
190
+ end
191
+
192
+ def teardown(name)
193
+ unless File.exist?("linode/instances/#{name}.yml")
194
+ say shell.set_color("#{name}.yml was not found in the instances directory.", :red, true)
195
+ abort
196
+ end
197
+ @config = YAML.load(File.read('linode/linode.yml'))
198
+ setup_route53 if @config['dns'] == 'route53'
199
+
200
+ @instance = YAML.load(File.read("linode/instances/#{name}.yml"))
201
+ @api = ::Linode.new(:api_key => @config['api_key'])
202
+
203
+ # Shutdown first or disk deletion will fail
204
+ say shell.set_color("shutting down...", :green, true)
205
+ @api.linode.shutdown(:LinodeID => @instance[:linode_id])
206
+ sleep 10
207
+
208
+ # Delete the disks. It is required - http://www.linode.com/api/linode/linode%2Edelete
209
+ say shell.set_color("deleting root disk...", :green, true)
210
+ @api.linode.disk.delete(:LinodeID => @instance[:linode_id], :DiskID => @instance[:root_diskid]) rescue nil
211
+ say shell.set_color("deleting swap disk...", :green, true)
212
+ @api.linode.disk.delete(:LinodeID => @instance[:linode_id], :DiskID => @instance[:swap_diskid]) rescue nil
213
+ sleep 5
214
+
215
+ # Delete the instance
216
+ say shell.set_color("deleting linode...", :green, true)
217
+ @api.linode.delete(:LinodeID => @instance[:linode_id])
218
+
219
+ # Delete DNS record
220
+ case @config['dns']
221
+ when 'linode'
222
+ # Set the public IP to Linode DNS Manager
223
+ say "deleting the public IP to Linode DNS Manager..."
224
+ @domainid = @api.domain.list.find{|i| i.domain == @config['fqdn']['zone'] }.domainid
225
+ @resource = @api.domain.resource.list(:DomainID => @domainid).find{|i| i.target == @instance[:public_ip] }
226
+ @api.domain.resource.delete(:DomainID => @domainid, :ResourceID => @resource.resourceid)
227
+ when 'route53'
228
+ # Set the public IP to AWS Route 53
229
+ say "deleting the public IP to AWS Route 53..."
230
+ @record = @route53_zone.get_records.find{|i| i.values.first == @instance[:public_ip] }
231
+ @record.delete
232
+ end
233
+
234
+ # Remove the instance config file
235
+ remove_file "linode/instances/#{name}.yml"
236
+
237
+ say shell.set_color("Done.", :green, true)
238
+ end
239
+
240
+ def setup_route53
241
+ Sunzi::Dependency.load('route53')
242
+ route53 = Route53::Connection.new(@config['route53']['key'], @config['route53']['secret'])
243
+ @route53_zone = route53.get_zones.find{|i| i.name.sub(/\.$/,'') == @config['fqdn']['zone'] }
244
+ end
245
+ end
246
+ end
247
+ end
248
+ end
@@ -0,0 +1,38 @@
1
+ module Sunzi
2
+ class Dependency
3
+ def self.all
4
+ {
5
+ 'linode' => {
6
+ :require => 'linode',
7
+ :version => '>= 0.7.7',
8
+ },
9
+ 'highline' => {
10
+ :require => 'highline',
11
+ :version => '>= 1.6.11',
12
+ },
13
+ 'route53' => {
14
+ :require => 'route53',
15
+ :version => '>= 0.2.1',
16
+ },
17
+ }
18
+ end
19
+
20
+ def self.load(name)
21
+ begin
22
+ gem(name, all[name][:version])
23
+ require(all[name][:require])
24
+ rescue LoadError
25
+ puts <<-EOS
26
+ Dependency missing: #{name}
27
+ To install the gem, issue the following command:
28
+
29
+ gem install #{name} -v '#{all[name][:version]}'
30
+
31
+ Please try again after installing the missing dependency.
32
+ EOS
33
+ exit 1
34
+ end
35
+ end
36
+
37
+ end
38
+ end
@@ -1,3 +1,3 @@
1
1
  module Sunzi
2
- VERSION = "0.3.0"
2
+ VERSION = "0.4.0"
3
3
  end
@@ -0,0 +1,34 @@
1
+ ---
2
+ api_key: your_api_key
3
+ root_pass: your_root_password
4
+ root_sshkey_path: ~/.ssh/id_rsa.pub
5
+
6
+ # payment_term must be 1, 12 or 24
7
+ payment_term: 1
8
+
9
+ # Add / remove environments
10
+ environments:
11
+ - production
12
+ - staging
13
+ fqdn:
14
+ zone: example.com
15
+ production: %{host}.example.com
16
+ staging: %{host}.staging.example.com
17
+ label:
18
+ production: example-%{host}
19
+ staging: example-staging-%{host}
20
+ group:
21
+ production: example
22
+ staging: example-staging
23
+
24
+ # Filter out large lists by keyword
25
+ distributions_filter: debian
26
+ kernels_filter: latest
27
+
28
+ # dns takes either "linode" or "route53"
29
+ dns: linode
30
+
31
+ # only used when route53 is chosen for DNS
32
+ route53:
33
+ key: your_aws_key
34
+ secret: your_aws_secret
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.3.0
4
+ version: 0.4.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-02-25 00:00:00.000000000 Z
12
+ date: 2012-02-26 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: thor
16
- requirement: &2152751900 !ruby/object:Gem::Requirement
16
+ requirement: &2156460860 !ruby/object:Gem::Requirement
17
17
  none: false
18
18
  requirements:
19
19
  - - ! '>='
@@ -21,7 +21,7 @@ dependencies:
21
21
  version: '0'
22
22
  type: :runtime
23
23
  prerelease: false
24
- version_requirements: *2152751900
24
+ version_requirements: *2156460860
25
25
  description: Server provisioning utility for minimalists
26
26
  email:
27
27
  - kenn.ejima@gmail.com
@@ -36,12 +36,16 @@ files:
36
36
  - Rakefile
37
37
  - bin/sunzi
38
38
  - lib/sunzi.rb
39
- - lib/sunzi/base.rb
40
39
  - lib/sunzi/cli.rb
40
+ - lib/sunzi/cloud/base.rb
41
+ - lib/sunzi/cloud/ec2.rb
42
+ - lib/sunzi/cloud/linode.rb
43
+ - lib/sunzi/dependency.rb
41
44
  - lib/sunzi/version.rb
42
- - lib/templates/remote/install.sh
43
- - lib/templates/remote/recipes/ssh_key.sh
44
- - lib/templates/sunzi.yml
45
+ - lib/templates/create/remote/install.sh
46
+ - lib/templates/create/remote/recipes/ssh_key.sh
47
+ - lib/templates/create/sunzi.yml
48
+ - lib/templates/setup/linode/linode.yml
45
49
  - sunzi.gemspec
46
50
  - test/test_cli.rb
47
51
  homepage: http://github.com/kenn/sunzi
@@ -1,9 +0,0 @@
1
- require 'yaml'
2
-
3
- module Sunzi
4
- class Base
5
- def initialize(project)
6
- # Do something
7
- end
8
- end
9
- end