chake 0.0.1 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore CHANGED
@@ -15,3 +15,5 @@ spec/reports
15
15
  test/tmp
16
16
  test/version_tmp
17
17
  tmp
18
+ /examples/test/nodes.yaml
19
+ /examples/test/.tmp
data/Rakefile CHANGED
@@ -1 +1,7 @@
1
1
  require "bundler/gem_tasks"
2
+
3
+ task :default do
4
+ sh 'rspec', '--color'
5
+ end
6
+
7
+ task :release => :default
data/chake.gemspec CHANGED
@@ -20,6 +20,7 @@ Gem::Specification.new do |spec|
20
20
 
21
21
  spec.add_development_dependency "bundler", "~> 1.5"
22
22
  spec.add_development_dependency "rake"
23
+ spec.add_development_dependency "rspec"
23
24
 
24
25
  spec.add_dependency "rake"
25
26
  end
@@ -1,9 +1,5 @@
1
- begin
2
- require 'chake'
3
- rescue LoadError
4
- $: << '../../lib'
5
- retry
6
- end
1
+ $: << '../../lib' # this shouldn't be needed when you have chake installed
2
+ require 'chake'
7
3
 
8
4
  desc 'removes everything'
9
5
  task :clean do
@@ -0,0 +1,18 @@
1
+ -----BEGIN PGP MESSAGE-----
2
+ Version: GnuPG v1
3
+
4
+ hQIMA5A8ZkAWdYz7AQ//dgQhKXuGS0dY04TXa1cXEtOHYC64LLnoo+UZT/KgnkzI
5
+ /IAgLFbAV0Fd9DGLK857qv9fWf/FB5b6loYLPOVEys0mb5aQrqPPFeKwTTXxTJdk
6
+ Ts2NvSVfNZx95Igotm/vkKW6/dbGlDfQSOVQMzhvmKOMMpRP+ixqn+G/nwE/0wQG
7
+ QXc3UPAe9gBFlI9GWLTafhftwrxXiYeNF8N03W9BUk1OiQqJUmDRK9ZjVe/vbEiZ
8
+ iQ689MnF5l+/6gptU0j77QIqk5vEItkl7RISxOS8PDFI+926NV3fZUrRAlu2eDIy
9
+ HNMGkcKTjGoPNrj/UzzmqjP3uNQtKoK559cul7uY44QmsEINoFP3HoAOHeVxm1pG
10
+ 6IG5EY6znwVEYTIVPK21NAPFpfk9yAB53sv6GrtOaYp8FFp+lLHJlPQcOOP51UV9
11
+ GqiAuec7Lgr3iy1yaXIBHFlLG0lmZ1OI41zwqh+Z8EF1NC0gFPhGuOJqBGmBbOxy
12
+ Wt1IFx1JUHi0f277us9pFbQlcUb8tgFZ0J69epBrod+xWaZQKFwLWsCTN66fKJfp
13
+ rqBzRBcNDJwsKOd54v/Cmrws8bwnfB8iNZYuEQdEt9u5TGIZFl4R9k+/pydAQouP
14
+ Z95g4vh2ST2ZEeblnbCc2TFY/7j2O+aXyBzX/4+/bM0kZbFjxCNuefU/v3ZnDwXS
15
+ QAH+NPQ31IiqMmoETUqHzo1UI+hV9em81Llt2bsbgWULp84LEmzczbjAcMi2EWNt
16
+ SzPyJzOLrqEvEYg2O/+bGho=
17
+ =Y2iZ
18
+ -----END PGP MESSAGE-----
@@ -0,0 +1,6 @@
1
+ file '/tmp/chake.test' do
2
+ content "It works!\n"
3
+ mode '0644'
4
+ owner 'root'
5
+ group 'root'
6
+ end
data/lib/chake.rb CHANGED
@@ -5,21 +5,23 @@ require 'json'
5
5
  require 'tmpdir'
6
6
  require 'readline'
7
7
 
8
- $nodes_file = ENV['NODES'] || 'nodes.yaml'
9
- $node_data = File.exists?($nodes_file) && YAML.load_file($nodes_file) || {}
10
- $nodes = $node_data.keys
8
+ require 'chake/node'
9
+
10
+ nodes_file = ENV['NODES'] || 'nodes.yaml'
11
+ node_data = File.exists?(nodes_file) && YAML.load_file(nodes_file) || {}
12
+ $nodes = node_data.map { |node,data| Chake::Node.new(node, data) }
11
13
 
12
- $sample_nodes = <<EOF
13
- host1.my.tld:
14
- run_list:
15
- - recipe[myhost]
16
- EOF
17
14
 
18
15
  desc "Initializes current directory with sample structure"
19
16
  task :init do
20
17
  if !File.exists?('nodes.yaml')
21
18
  File.open('nodes.yaml', 'w') do |f|
22
- f.write($sample_nodes)
19
+ sample_nodes = <<EOF
20
+ host1.my.tld:
21
+ run_list:
22
+ - recipe[myhost]
23
+ EOF
24
+ f.write(sample_nodes)
23
25
  puts "→ nodes.yaml"
24
26
  end
25
27
  end
@@ -45,7 +47,9 @@ end
45
47
 
46
48
  desc 'list nodes'
47
49
  task :nodes do
48
- puts $nodes
50
+ $nodes.each do |node|
51
+ puts "%-40s %-5s\n" % [node.hostname, node.backend]
52
+ end
49
53
  end
50
54
 
51
55
  def encrypted_for(node)
@@ -71,23 +75,25 @@ end
71
75
 
72
76
  $nodes.each do |node|
73
77
 
74
- desc "bootstrap #{node}"
75
- task "bootstrap:#{node}" do
78
+ hostname = node.hostname
79
+
80
+ desc "bootstrap #{hostname}"
81
+ task "bootstrap:#{hostname}" do
76
82
  mkdir_p '.tmp', :verbose => false
77
- config = '.tmp/' + node + '.json'
83
+ config = '.tmp/' + hostname + '.json'
78
84
 
79
85
  seen_before = File.exists?(config)
80
86
 
81
87
  # overwrite config with current contents
82
88
  File.open(config, 'w') do |f|
83
- json_data = $node_data[node]
89
+ json_data = node.data
84
90
  f.write(JSON.dump(json_data))
85
91
  f.write("\n")
86
92
  end
87
93
 
88
94
  unless seen_before
89
95
  begin
90
- sh "ssh root@#{node} 'apt-get -q -y install rsync chef'"
96
+ node.run_as_root('apt-get -q -y install rsync chef')
91
97
  rescue
92
98
  rm_f config
93
99
  raise
@@ -96,41 +102,40 @@ $nodes.each do |node|
96
102
 
97
103
  end
98
104
 
99
- desc "upload data to #{node}"
100
- task "upload:#{node}" do
101
- encrypted = encrypted_for(node)
102
- excludes = encrypted.values.map { |f| "--exclude #{f}" }.join(' ')
105
+ desc "upload data to #{hostname}"
106
+ task "upload:#{hostname}" do
107
+ encrypted = encrypted_for(hostname)
108
+ rsync_excludes = (encrypted.values + encrypted.keys).map { |f| ["--exclude", f] }.flatten
103
109
 
104
- rsync = "rsync -avp --delete --exclude .git/ #{excludes}"
110
+ rsync = "rsync", "-avp", "--exclude", ".git/"
111
+ rsync_logging = Rake.application.options.silent && '--quiet' || '--verbose'
105
112
 
106
- Dir.mktmpdir do |tmpdir|
107
- sh "#{rsync} --quiet ./ #{tmpdir}/"
108
- files = Dir.chdir(tmpdir) { Dir.glob("**/*").select { |f| !File.directory?(f) } }
109
- if_files_changed(node, 'plain', files) do
110
- rsync_logging = Rake.application.options.silent && '--quiet' || '--verbose'
111
- sh "#{rsync} #{rsync_logging} ./ root@#{node}:/srv/chef/"
112
- end
113
+ files = Dir.glob("**/*").select { |f| !File.directory?(f) } - encrypted.keys - encrypted.values
114
+ if_files_changed(hostname, 'plain', files) do
115
+ sh *rsync, '--delete', rsync_logging, *rsync_excludes, './', node.rsync_dest
113
116
  end
114
117
 
115
- if_files_changed(node, 'enc', encrypted.keys) do
116
- encrypted.each do |encrypted_file, target_file|
117
- sh "gpg --quiet --batch --use-agent --decrypt #{encrypted_file} | ssh root@#{node} 'cat > /srv/chef/#{target_file}; chmod 600 /srv/chef/#{target_file}'"
118
+ if_files_changed(hostname, 'enc', encrypted.keys) do
119
+ Dir.mktmpdir do |tmpdir|
120
+ encrypted.each do |encrypted_file, target_file|
121
+ target = File.join(tmpdir, target_file)
122
+ mkdir_p(File.dirname(target))
123
+ sh 'gpg', '--quiet', '--batch', '--use-agent', '--output', target, '--decrypt', encrypted_file
124
+ end
125
+ sh *rsync, rsync_logging, tmpdir + '/', node.rsync_dest
118
126
  end
119
127
  end
120
128
  end
121
129
 
122
- desc "converge #{node}"
123
- task "converge:#{node}" => ["bootstrap:#{node}", "upload:#{node}"] do
130
+ desc "converge #{hostname}"
131
+ task "converge:#{hostname}" => ["bootstrap:#{hostname}", "upload:#{hostname}"] do
124
132
  chef_logging = Rake.application.options.silent && '-l fatal' || ''
125
- sh "ssh root@#{node} 'chef-solo -c /srv/chef/config.rb #{chef_logging} -j /srv/chef/.tmp/#{node}.json'"
133
+ node.run_as_root "chef-solo -c #{node.path}/config.rb #{chef_logging} -j #{node.path}/.tmp/#{hostname}.json"
126
134
  end
127
135
 
128
- desc "run a command on #{node}"
129
- task "run:#{node}" => 'run_input' do
130
- cmd = ['ssh', "root@#{node}", $cmd]
131
- IO.popen(cmd).lines.each do |line|
132
- puts "#{node}: #{line}"
133
- end
136
+ desc "run a command on #{hostname}"
137
+ task "run:#{hostname}" => 'run_input' do
138
+ node.run($cmd)
134
139
  end
135
140
  end
136
141
 
@@ -139,15 +144,15 @@ task :run_input do
139
144
  end
140
145
 
141
146
  desc "upload to all nodes"
142
- task :upload => $nodes.map { |node| "upload:#{node}" }
147
+ task :upload => $nodes.map { |node| "upload:#{node.hostname}" }
143
148
 
144
149
  desc "bootstrap all nodes"
145
- task :bootstrap => $nodes.map { |node| "bootstrap:#{node}" }
150
+ task :bootstrap => $nodes.map { |node| "bootstrap:#{node.hostname}" }
146
151
 
147
152
  desc "converge all nodes (default)"
148
- task "converge" => $nodes.map { |node| "converge:#{node}" }
153
+ task "converge" => $nodes.map { |node| "converge:#{node.hostname}" }
149
154
 
150
155
  task "run a command on all nodes"
151
- task :run => $nodes.map { |node| "run:#{node}" }
156
+ task :run => $nodes.map { |node| "run:#{node.hostname}" }
152
157
 
153
158
  task :default => :converge
@@ -0,0 +1,46 @@
1
+ module Chake
2
+
3
+ class Backend < Struct.new(:node)
4
+
5
+ def rsync_dest
6
+ node.path + '/'
7
+ end
8
+
9
+ def run(cmd)
10
+ IO.popen(command_runner + [cmd]).lines.each do |line|
11
+ puts [node.hostname, line.strip].join(': ')
12
+ end
13
+ end
14
+
15
+ def run_as_root(cmd)
16
+ if node.username == 'root'
17
+ run(cmd)
18
+ else
19
+ run('sudo ' + cmd)
20
+ end
21
+ end
22
+
23
+ def to_s
24
+ self.class.backend_name
25
+ end
26
+
27
+ def self.backend_name
28
+ name.split("::").last.downcase
29
+ end
30
+
31
+ def self.inherited(subclass)
32
+ @backends ||= []
33
+ @backends << subclass
34
+ end
35
+
36
+ def self.get(name)
37
+ backend = @backends.find { |b| b.backend_name == name }
38
+ backend || raise(ArgumentError.new("Invalid backend name: #{name}"))
39
+ end
40
+
41
+ end
42
+
43
+ end
44
+
45
+ require 'chake/backend/ssh'
46
+ require 'chake/backend/local'
@@ -0,0 +1,15 @@
1
+ module Chake
2
+
3
+ class Backend
4
+
5
+ class Local < Backend
6
+
7
+ def command_runner
8
+ ['sh', '-c']
9
+ end
10
+
11
+ end
12
+
13
+ end
14
+
15
+ end
@@ -0,0 +1,25 @@
1
+ module Chake
2
+
3
+ class Backend
4
+
5
+ class Ssh < Backend
6
+
7
+ def rsync_dest
8
+ [ssh_target, node.path + '/'].join(':')
9
+ end
10
+
11
+ def command_runner
12
+ ['ssh', ssh_target]
13
+ end
14
+
15
+ private
16
+
17
+ def ssh_target
18
+ [node.username, node.hostname].compact.join('@')
19
+ end
20
+
21
+ end
22
+
23
+ end
24
+
25
+ end
data/lib/chake/node.rb ADDED
@@ -0,0 +1,44 @@
1
+ require 'uri'
2
+ require 'etc'
3
+ require 'forwardable'
4
+
5
+ require 'chake/backend'
6
+
7
+ module Chake
8
+
9
+ class Node
10
+
11
+ extend Forwardable
12
+
13
+ attr_reader :hostname
14
+ attr_reader :username
15
+ attr_reader :path
16
+ attr_reader :data
17
+
18
+ def initialize(hostname, data = {})
19
+ uri = URI.parse(hostname)
20
+ if !uri.scheme && !uri.host && uri.path
21
+ uri = URI.parse("ssh://#{hostname}")
22
+ end
23
+ if uri.path.empty?
24
+ uri.path = nil
25
+ end
26
+
27
+ @backend_name = uri.scheme
28
+
29
+ @hostname = uri.hostname
30
+ @username = uri.user || Etc.getpwuid.name
31
+ @path = uri.path || "/tmp/chef.#{username}"
32
+ @data = data
33
+ end
34
+
35
+ def backend
36
+ @backend ||= Chake::Backend.get(@backend_name).new(self)
37
+ end
38
+
39
+ def_delegators :backend, :run, :run_as_root, :rsync_dest
40
+
41
+ end
42
+
43
+ end
44
+
data/lib/chake/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Chake
2
- VERSION = "0.0.1"
2
+ VERSION = "0.1.0"
3
3
  end
@@ -0,0 +1,12 @@
1
+ require 'spec_helper'
2
+
3
+ describe Chake::Backend::Local do
4
+
5
+ include_examples "Chake::Backend", Chake::Backend::Local
6
+
7
+ let(:node) { Chake::Node.new('local://myhost/srv/chef') }
8
+
9
+ it('runs commands with sh -c') { expect(backend.command_runner).to eq(['sh', '-c']) }
10
+
11
+ it('rsyncs locally') { expect(backend.rsync_dest).to eq('/srv/chef/') }
12
+ end
@@ -0,0 +1,13 @@
1
+ require 'spec_helper'
2
+
3
+ describe Chake::Backend::Ssh do
4
+
5
+ include_examples "Chake::Backend", Chake::Backend::Ssh, ->() { }
6
+
7
+ let(:node) { Chake::Node.new('ssh://myuser@myhost/srv/chef') }
8
+
9
+ it('runs commands with ssh') { expect(backend.command_runner).to eq(['ssh', 'myuser@myhost']) }
10
+
11
+ it('rsyncs over ssh') { expect(backend.rsync_dest).to eq('myuser@myhost:/srv/chef/') }
12
+
13
+ end
@@ -0,0 +1,2 @@
1
+ require 'chake/backend'
2
+
@@ -0,0 +1,50 @@
1
+ require 'chake/node'
2
+
3
+ describe Chake::Node do
4
+
5
+ before do
6
+ ent = double
7
+ ent.stub(:name).and_return('jonhdoe')
8
+ Etc.stub(:getpwuid).and_return(ent)
9
+ end
10
+
11
+ let(:simple) { Chake::Node.new('hostname') }
12
+ it('has a name') { expect(simple.hostname).to eq('hostname') }
13
+ it('uses ssh by default') { expect(simple.backend).to be_an_instance_of(Chake::Backend::Ssh) }
14
+ it('user current username by default') {
15
+ expect(simple.username).to eq('jonhdoe')
16
+ }
17
+ it('write to /tmp/chef.$username') {
18
+ expect(simple.path).to eq('/tmp/chef.jonhdoe')
19
+ }
20
+
21
+ let(:with_username) { Chake::Node.new('username@hostname') }
22
+ it('accepts username') { expect(with_username.username).to eq('username') }
23
+ it('uses ssh') { expect(with_username.backend).to be_an_instance_of(Chake::Backend::Ssh) }
24
+
25
+ let(:with_backend) { Chake::Node.new('local://hostname')}
26
+ it('accepts backend as URI scheme') { expect(with_backend.backend).to be_an_instance_of(Chake::Backend::Local) }
27
+
28
+ it('wont accept any backend') do
29
+ expect { Chake::Node.new('foobar://bazqux').backend }.to raise_error(ArgumentError)
30
+ end
31
+
32
+ let(:with_data) { Chake::Node.new('local://localhost', 'run_list' => ['recipe[common]']) }
33
+ it('takes data') do
34
+ expect(with_data.data).to be_a(Hash)
35
+ end
36
+
37
+ [:run, :run_as_root, :rsync_dest].each do |method|
38
+ it("delegates #{method} to backend") do
39
+ node = simple
40
+
41
+ backend = double
42
+ args = Object.new
43
+ node.stub(:backend).and_return(backend)
44
+
45
+ backend.should_receive(method).with(args)
46
+ node.send(method, args)
47
+ end
48
+ end
49
+
50
+ end
@@ -0,0 +1,29 @@
1
+ require 'chake/node'
2
+ require 'chake/backend'
3
+
4
+
5
+ shared_examples "Chake::Backend" do |backend_class|
6
+
7
+ let(:backend) { backend_class.new(node) }
8
+
9
+ it('runs commands') do
10
+ io = StringIO.new("line 1\nline 2\n")
11
+ IO.should_receive(:popen).with(backend.command_runner + ['something']).and_return(io)
12
+ backend.should_receive(:puts).with("myhost: line 1")
13
+ backend.should_receive(:puts).with("myhost: line 2")
14
+ backend.run('something')
15
+ end
16
+
17
+ it('runs as root') do
18
+ backend.should_receive(:run).with('sudo something')
19
+ backend.run_as_root('something')
20
+ end
21
+
22
+ it('does not use sudo if already root') do
23
+ backend.node.stub(:username).and_return('root')
24
+ backend.should_receive(:run).with('something')
25
+ backend.run_as_root('something')
26
+ end
27
+
28
+ end
29
+
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: chake
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.1.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2014-02-21 00:00:00.000000000 Z
12
+ date: 2014-02-22 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: bundler
@@ -43,6 +43,22 @@ dependencies:
43
43
  - - ! '>='
44
44
  - !ruby/object:Gem::Version
45
45
  version: '0'
46
+ - !ruby/object:Gem::Dependency
47
+ name: rspec
48
+ requirement: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ! '>='
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
54
+ type: :development
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ! '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
46
62
  - !ruby/object:Gem::Dependency
47
63
  name: rake
48
64
  requirement: !ruby/object:Gem::Requirement
@@ -74,13 +90,21 @@ files:
74
90
  - README.md
75
91
  - Rakefile
76
92
  - chake.gemspec
77
- - examples/test/.tmp/lvh.me.plain.sha1sum
78
93
  - examples/test/Rakefile
79
94
  - examples/test/config.rb
80
- - examples/test/cookbooks/myhost/recipes/default.rb
81
- - examples/test/nodes.yaml
95
+ - examples/test/cookbooks/example/files/default/test.asc
96
+ - examples/test/cookbooks/example/recipes/default.rb
82
97
  - lib/chake.rb
98
+ - lib/chake/backend.rb
99
+ - lib/chake/backend/local.rb
100
+ - lib/chake/backend/ssh.rb
101
+ - lib/chake/node.rb
83
102
  - lib/chake/version.rb
103
+ - spec/chake/backend/local_spec.rb
104
+ - spec/chake/backend/ssh_spec.rb
105
+ - spec/chake/backend_spec.rb
106
+ - spec/chake/node_spec.rb
107
+ - spec/spec_helper.rb
84
108
  homepage: https://github.com/terceiro/chake
85
109
  licenses:
86
110
  - MIT
@@ -106,4 +130,9 @@ rubygems_version: 1.8.23
106
130
  signing_key:
107
131
  specification_version: 3
108
132
  summary: Simple host management with chef and rake. No chef server required.
109
- test_files: []
133
+ test_files:
134
+ - spec/chake/backend/local_spec.rb
135
+ - spec/chake/backend/ssh_spec.rb
136
+ - spec/chake/backend_spec.rb
137
+ - spec/chake/node_spec.rb
138
+ - spec/spec_helper.rb
@@ -1,4 +0,0 @@
1
- a70248580db38a6659bba1887c16ead12d8d2f6c config.rb
2
- 3f89ae2a25368bb08d8ecbe1b12028cdbd29bb23 nodes.yaml
3
- 7e805ef45e1ee657afb162fd98e882caf9113bc0 cookbooks/myhost/recipes/default.rb
4
- ff86a66c0e8bb3dd6f526ab94ffd57f23c7153f1 Rakefile
@@ -1 +0,0 @@
1
- package 'openssh-server'
@@ -1,3 +0,0 @@
1
- lvh.me:
2
- run_list:
3
- - recipe[myhost]