dust-deploy 0.16.4 → 0.16.5

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,40 @@
1
+ require 'dust/server/ssh'
2
+
3
+ module Dust
4
+ class Server
5
+ def selinuxenabled?
6
+ return true if exec('selinuxenabled')[:exit_code] == 0
7
+ false
8
+ end
9
+
10
+ # check if restorecon (selinux) is available
11
+ # if so, run it on "path" recursively
12
+ def restorecon(path, options={})
13
+ options = default_options.merge(options)
14
+
15
+ # if selinux is not enabled, just return
16
+ return true unless selinuxenabled?
17
+
18
+ msg = messages.add("restoring selinux labels for #{path}", options)
19
+ msg.parse_result(exec("restorecon -R #{path}")[:exit_code])
20
+ end
21
+
22
+ def chcon(permissions, file, options={})
23
+ options = default_options.merge(options)
24
+
25
+ # just return if selinux is not enabled
26
+ return true unless selinuxenabled?
27
+
28
+ args = ""
29
+ args << " --type #{permissions['type']}" if permissions['type']
30
+ args << " --recursive #{permissions['recursive']}" if permissions['recursive']
31
+ args << " --user #{permissions['user']}" if permissions['user']
32
+ args << " --range #{permissions['range']}" if permissions['range']
33
+ args << " --role #{permissions['role']}" if permissions['role']
34
+
35
+ msg = messages.add("setting selinux permissions of #{File.basename(file)}", options)
36
+ msg.parse_result(exec("chcon #{args} #{file}")[:exit_code])
37
+ end
38
+
39
+ end
40
+ end
@@ -0,0 +1,82 @@
1
+ require 'dust/server/ssh'
2
+
3
+ module Dust
4
+ class Server
5
+ def autostart_service(service, options={})
6
+ options = default_options.merge(options)
7
+
8
+ msg = messages.add("autostart #{service} on boot", options)
9
+
10
+ if uses_rpm?
11
+ if file_exists? '/bin/systemctl', :quiet => true
12
+ msg.parse_result(exec("systemctl enable #{service}.service")[:exit_code])
13
+ else
14
+ msg.parse_result(exec("chkconfig #{service} on")[:exit_code])
15
+ end
16
+
17
+ elsif uses_apt?
18
+ msg.parse_result(exec("update-rc.d #{service} defaults")[:exit_code])
19
+
20
+ elsif uses_emerge?
21
+ msg.parse_result(exec("rc-update add #{service} default")[:exit_code])
22
+
23
+ # archlinux needs his autostart daemons in /etc/rc.conf, in the DAEMONS line
24
+ #elsif uses_pacman?
25
+
26
+ else
27
+ msg.failed
28
+ end
29
+ end
30
+
31
+ # invoke 'command' on the service (e.g. @node.service 'postgresql', 'restart')
32
+ def service(service, command, options={})
33
+ options = default_options.merge(options)
34
+
35
+ return messages.add("service: '#{service}' unknown", options).failed unless service.is_a? String
36
+
37
+ # try systemd, then upstart, then sysvconfig, then rc.d, then initscript
38
+ if file_exists? '/bin/systemctl', :quiet => true
39
+ msg = messages.add("#{command}ing #{service} (via systemd)", options)
40
+ ret = exec("systemctl #{command} #{service}.service")
41
+
42
+ elsif file_exists? "/etc/init/#{service}", :quiet => true
43
+ msg = messages.add("#{command}ing #{service} (via upstart)", options)
44
+ ret = exec("#{command} #{service}")
45
+
46
+ elsif file_exists? '/sbin/service', :quiet => true or file_exists? '/usr/sbin/service', :quiet => true
47
+ msg = messages.add("#{command}ing #{service} (via sysvconfig)", options)
48
+ ret = exec("service #{service} #{command}")
49
+
50
+ elsif file_exists? '/usr/sbin/rc.d', :quiet => true
51
+ msg = messages.add("#{command}ing #{service} (via rc.d)", options)
52
+ ret = exec("rc.d #{command} #{service}")
53
+
54
+ else
55
+ msg = messages.add("#{command}ing #{service} (via initscript)", options)
56
+ ret = exec("/etc/init.d/#{service} #{command}")
57
+ end
58
+
59
+ msg.parse_result(ret[:exit_code])
60
+ ret
61
+ end
62
+
63
+ def restart_service(service, options={})
64
+ options = default_options.merge(options)
65
+
66
+ service(service, 'restart', options)
67
+ end
68
+
69
+ def reload_service(service, options={})
70
+ options = default_options.merge(options)
71
+
72
+ service(service, 'reload', options)
73
+ end
74
+
75
+ def print_service_status(service, options={})
76
+ options = default_options.merge(:indent => 0).merge(options)
77
+ ret = service(service, 'status', options)
78
+ messages.add('', options).print_output(ret)
79
+ ret
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,182 @@
1
+ require 'net/ssh'
2
+ require 'net/scp'
3
+ require 'net/ssh/proxy/socks5'
4
+
5
+ require 'dust/server/file'
6
+ require 'dust/server/osdetect'
7
+ require 'dust/server/package'
8
+ require 'dust/server/selinux'
9
+
10
+ module Dust
11
+ class Server
12
+ attr_reader :ssh
13
+
14
+ def connect
15
+ messages.print_hostname_header(@node['hostname']) unless $parallel
16
+
17
+ begin
18
+ # connect to proxy if given
19
+ if @node['proxy']
20
+ host, port = @node['proxy'].split ':'
21
+ proxy = Net::SSH::Proxy::SOCKS5.new(host, port)
22
+ else
23
+ proxy = nil
24
+ end
25
+
26
+ @ssh = Net::SSH.start @node['fqdn'], @node['user'],
27
+ { :password => @node['password'],
28
+ :port => @node['port'],
29
+ :proxy => proxy }
30
+ rescue Exception
31
+ error_message = "coudln't connect to #{@node['fqdn']}"
32
+ error_message << " (via socks5 proxy #{@node['proxy']})" if proxy
33
+ messages.add(error_message, :indent => 0).failed
34
+ return false
35
+ end
36
+
37
+ true
38
+ end
39
+
40
+ def disconnect
41
+ @ssh.close
42
+ end
43
+
44
+ def exec(command, options={:live => false, :as_user => false})
45
+ sudo_authenticated = false
46
+ stdout = ''
47
+ stderr = ''
48
+ exit_code = nil
49
+ exit_signal = nil
50
+
51
+ # prepend a newline, if output is live
52
+ messages.add("\n", :indent => 0) if options[:live]
53
+
54
+ @ssh.open_channel do |channel|
55
+
56
+ # if :as_user => user is given, execute as user (be aware of ' usage)
57
+ command = "su #{options[:as_user]} -l -c '#{command}'" if options[:as_user]
58
+
59
+ # request a terminal (sudo needs it)
60
+ # and prepend "sudo"
61
+ # command is wrapped in ", escapes " in the command string
62
+ # and then executed using "sh -c", so that
63
+ # the use of > < && || | and ; doesn't screw things up
64
+ if @node['sudo']
65
+ channel.request_pty
66
+ command = "sudo -k -- sh -c \"#{command.gsub('"','\\"')}\""
67
+ end
68
+
69
+ channel.exec command do |ch, success|
70
+ abort "FAILED: couldn't execute command (ssh.channel.exec)" unless success
71
+
72
+ channel.on_data do |ch, data|
73
+ # only send password if sudo mode is enabled,
74
+ # and only send password once in a session (trying to prevent attacks reading out the password)
75
+ if data =~ /\[sudo\] password for #{@node['user']}/
76
+
77
+ raise 'password requested, but none given in config!' if @node['password'].empty?
78
+ raise 'already sent password, but sudo requested the password again. (wrong password?)' if sudo_authenticated
79
+
80
+ # we're not authenticated yet, send password
81
+ channel.send_data "#{@node['password']}\n"
82
+ sudo_authenticated = true
83
+
84
+ else
85
+ # skip everything util authenticated (if sudo is used and password given in config)
86
+ next if @node['sudo'] and not @node['password'].empty? and not sudo_authenticated
87
+
88
+ stdout += data
89
+ messages.add(data.green, :indent => 0) if options[:live] and not data.empty?
90
+ end
91
+ end
92
+
93
+ channel.on_extended_data do |ch, type, data|
94
+ stderr += data
95
+ messages.add(data.red, :indent => 0) if options[:live] and not data.empty?
96
+ end
97
+
98
+ channel.on_request('exit-status') { |ch, data| exit_code = data.read_long }
99
+ channel.on_request('exit-signal') { |ch, data| exit_signal = data.read_long }
100
+ end
101
+ end
102
+
103
+ @ssh.loop
104
+
105
+ # sudo usage provokes a heading newline that's unwanted.
106
+ stdout.sub! /^(\r\n|\n|\r)/, '' if @node['sudo']
107
+
108
+ { :stdout => stdout, :stderr => stderr, :exit_code => exit_code, :exit_signal => exit_signal }
109
+ end
110
+
111
+ def scp(source, destination, options={})
112
+ options = default_options.merge(options)
113
+
114
+ # make sure scp is installed on client
115
+ install_package('openssh-clients', :quiet => true) if uses_rpm?
116
+
117
+ msg = messages.add("deploying #{File.basename source}", options)
118
+
119
+ # check if destination is a directory
120
+ is_dir = dir_exists?(destination, :quiet => true)
121
+
122
+ # save permissions if the file already exists
123
+ ret = exec("stat -c %a:%u:%g #{destination}")
124
+ if ret[:exit_code] == 0 and not is_dir
125
+ permissions, user, group = ret[:stdout].chomp.split(':')
126
+ else
127
+ # files = 644, dirs = 755
128
+ permissions = 'ug-x,o-wx,u=rwX,g=rX,o=rX'
129
+ user = 'root'
130
+ group = 'root'
131
+ end
132
+
133
+ # if in sudo mode, copy file to temporary place, then move using sudo
134
+ if @node['sudo']
135
+ tmpdir = mktemp(:type => 'directory')
136
+ return msg.failed('could not create temporary directory (needed for sudo)') unless tmpdir
137
+
138
+ # temporary destination in tmpdir
139
+ tmpdest = "#{tmpdir}/#{File.basename(destination)}"
140
+
141
+ # allow user to write file without sudo (for scp)
142
+ # then change file back to root, and copy to the destination
143
+ chown(@node['user'], tmpdir, :quiet => true)
144
+ @ssh.scp.upload!(source, tmpdest, :recursive => true)
145
+
146
+ # set file permissions
147
+ chown("#{user}:#{group}", tmpdest, :quiet => true) if user and group
148
+ chmod(permissions, tmpdest, :quiet => true)
149
+
150
+ # if destination is a directory, append real filename
151
+ destination = "#{destination}/#{File.basename(source)}" if is_dir
152
+
153
+ # move the file from the temporary location to where it actually belongs
154
+ msg.parse_result(exec("mv -f #{tmpdest} #{destination}")[:exit_code])
155
+
156
+ # remove temporary directory
157
+ rm(tmpdir, :quiet => true)
158
+
159
+ else
160
+ @ssh.scp.upload!(source, destination, :recursive => true)
161
+ msg.ok
162
+
163
+ # set file permissions
164
+ chown("#{user}:#{group}", destination, :quiet => true) if user and group
165
+ chmod(permissions, destination, :quiet => true)
166
+ end
167
+
168
+ restorecon(destination, options) # restore SELinux labels
169
+ end
170
+
171
+ # download a file (sudo not yet supported)
172
+ def download(source, destination, options={})
173
+ options = default_options.merge(options)
174
+
175
+ # make sure scp is installed on client
176
+ install_package('openssh-clients', :quiet => true) if uses_rpm?
177
+
178
+ msg = messages.add("downloading #{File.basename source}", options)
179
+ msg.parse_result(@ssh.scp.download!(source, destination))
180
+ end
181
+ end
182
+ end
@@ -0,0 +1,117 @@
1
+ require 'dust/server/ssh'
2
+ require 'dust/server/selinux'
3
+
4
+ module Dust
5
+ class Server
6
+ def get_system_users(options={})
7
+ options = default_options.merge(options)
8
+
9
+ msg = messages.add("getting all system users", options)
10
+ ret = exec 'getent passwd |cut -d: -f1'
11
+ msg.parse_result(ret[:exit_code])
12
+
13
+ users = []
14
+ ret[:stdout].each do |user|
15
+ users.push(user.chomp)
16
+ end
17
+ users
18
+ end
19
+
20
+ # check whether a user exists on this node
21
+ def user_exists? user, options={}
22
+ options = default_options.merge(options)
23
+
24
+ msg = messages.add("checking if user #{user} exists", options)
25
+ msg.parse_result(exec("id #{user}")[:exit_code])
26
+ end
27
+
28
+ # manages users (create, modify)
29
+ def manage_user(user, options={})
30
+ options = default_options.merge(options)
31
+ options = { 'home' => nil, 'shell' => nil, 'uid' => nil, 'remove' => false,
32
+ 'gid' => nil, 'groups' => nil, 'system' => false }.merge(options)
33
+
34
+ # delete user from system
35
+ if options['remove']
36
+ if user_exists?(user, :quiet => true)
37
+ msg = messages.add("deleting user #{user} from system", { :indent => options[:indent] }.merge(options))
38
+ return msg.parse_result(exec("userdel --remove #{user}")[:exit_code])
39
+ end
40
+
41
+ return messages.add("user #{user} not present in system", options).ok
42
+ end
43
+
44
+ if user_exists?(user, :quiet => true)
45
+ args = ""
46
+ args << " --move-home --home #{options['home']}" if options['home']
47
+ args << " --shell #{options['shell']}" if options['shell']
48
+ args << " --uid #{options['uid']}" if options['uid']
49
+ args << " --gid #{options['gid']}" if options['gid']
50
+ args << " --append --groups #{Array(options['groups']).join(',')}" if options['groups']
51
+
52
+ if args.empty?
53
+ ret = messages.add("user #{user} already set up correctly", options).ok
54
+ else
55
+ msg = messages.add("modifying user #{user}", { :indent => options[:indent] }.merge(options))
56
+ ret = msg.parse_result(exec("usermod #{args} #{user}")[:exit_code])
57
+ end
58
+
59
+ else
60
+ args = ""
61
+ args = "--create-home" unless options['system']
62
+ args << " --system" if options['system']
63
+ args << " --home #{options['home']}" if options['home'] and not options['system']
64
+ args << " --shell #{options['shell']}" if options['shell']
65
+ args << " --uid #{options['uid']}" if options['uid']
66
+ args << " --gid #{options['gid']}" if options['gid']
67
+ args << " --groups #{Array(options['groups']).join(',')}" if options['groups']
68
+
69
+ msg = messages.add("creating user #{user}", { :indent => options[:indent] }.merge(options))
70
+ ret = msg.parse_result(exec("useradd #{user} #{args}")[:exit_code])
71
+ end
72
+
73
+ # set selinux permissions
74
+ chcon({ 'type' => 'user_home_dir_t' }, get_home(user), options)
75
+ return ret
76
+ end
77
+
78
+ # returns the home directory of this user
79
+ def get_home(user, options={})
80
+ options = default_options(:quiet => true).merge(options)
81
+
82
+ msg = messages.add("getting home directory of #{user}", options)
83
+ ret = exec("getent passwd |cut -d':' -f1,6 |grep '^#{user}' |head -n1 |cut -d: -f2")
84
+ if msg.parse_result(ret[:exit_code]) and not ret[:stdout].chomp.empty?
85
+ return ret[:stdout].chomp
86
+ else
87
+ return false
88
+ end
89
+ end
90
+
91
+ # returns shell of this user
92
+ def get_shell(user, options={})
93
+ options = default_options(:quiet => true).merge(options)
94
+
95
+ msg = messages.add("getting shell of #{user}", options)
96
+ ret = exec("getent passwd |cut -d':' -f1,7 |grep '^#{user}' |head -n1 |cut -d: -f2")
97
+ if msg.parse_result(ret[:exit_code])
98
+ return ret[:stdout].chomp
99
+ else
100
+ return false
101
+ end
102
+ end
103
+
104
+ # returns primary group id of this user
105
+ def get_gid(user, options={})
106
+ options = default_options(:quiet => true).merge(options)
107
+
108
+ msg = messages.add("getting primary gid of #{user}", options)
109
+ ret = exec("id -g #{user}")
110
+ if msg.parse_result(ret[:exit_code])
111
+ return ret[:stdout].chomp
112
+ else
113
+ return false
114
+ end
115
+ end
116
+ end
117
+ end
@@ -1,3 +1,3 @@
1
1
  module Dust
2
- VERSION = "0.16.4"
2
+ VERSION = "0.16.5"
3
3
  end
metadata CHANGED
@@ -1,126 +1,111 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: dust-deploy
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.16.4
5
- prerelease:
4
+ version: 0.16.5
6
5
  platform: ruby
7
6
  authors:
8
7
  - kris kechagia
9
8
  autorequire:
10
9
  bindir: bin
11
10
  cert_chain: []
12
- date: 2012-11-04 00:00:00.000000000 Z
11
+ date: 2013-05-31 00:00:00.000000000 Z
13
12
  dependencies:
14
13
  - !ruby/object:Gem::Dependency
15
14
  name: json
16
15
  requirement: !ruby/object:Gem::Requirement
17
- none: false
18
16
  requirements:
19
- - - ! '>='
17
+ - - '>='
20
18
  - !ruby/object:Gem::Version
21
19
  version: '0'
22
20
  type: :runtime
23
21
  prerelease: false
24
22
  version_requirements: !ruby/object:Gem::Requirement
25
- none: false
26
23
  requirements:
27
- - - ! '>='
24
+ - - '>='
28
25
  - !ruby/object:Gem::Version
29
26
  version: '0'
30
27
  - !ruby/object:Gem::Dependency
31
28
  name: net-ssh
32
29
  requirement: !ruby/object:Gem::Requirement
33
- none: false
34
30
  requirements:
35
- - - ! '>='
31
+ - - '>='
36
32
  - !ruby/object:Gem::Version
37
33
  version: '0'
38
34
  type: :runtime
39
35
  prerelease: false
40
36
  version_requirements: !ruby/object:Gem::Requirement
41
- none: false
42
37
  requirements:
43
- - - ! '>='
38
+ - - '>='
44
39
  - !ruby/object:Gem::Version
45
40
  version: '0'
46
41
  - !ruby/object:Gem::Dependency
47
42
  name: net-scp
48
43
  requirement: !ruby/object:Gem::Requirement
49
- none: false
50
44
  requirements:
51
- - - ! '>='
45
+ - - '>='
52
46
  - !ruby/object:Gem::Version
53
47
  version: '0'
54
48
  type: :runtime
55
49
  prerelease: false
56
50
  version_requirements: !ruby/object:Gem::Requirement
57
- none: false
58
51
  requirements:
59
- - - ! '>='
52
+ - - '>='
60
53
  - !ruby/object:Gem::Version
61
54
  version: '0'
62
55
  - !ruby/object:Gem::Dependency
63
56
  name: net-sftp
64
57
  requirement: !ruby/object:Gem::Requirement
65
- none: false
66
58
  requirements:
67
- - - ! '>='
59
+ - - '>='
68
60
  - !ruby/object:Gem::Version
69
61
  version: '0'
70
62
  type: :runtime
71
63
  prerelease: false
72
64
  version_requirements: !ruby/object:Gem::Requirement
73
- none: false
74
65
  requirements:
75
- - - ! '>='
66
+ - - '>='
76
67
  - !ruby/object:Gem::Version
77
68
  version: '0'
78
69
  - !ruby/object:Gem::Dependency
79
70
  name: thor
80
71
  requirement: !ruby/object:Gem::Requirement
81
- none: false
82
72
  requirements:
83
- - - ! '>='
73
+ - - '>='
84
74
  - !ruby/object:Gem::Version
85
75
  version: '0'
86
76
  type: :runtime
87
77
  prerelease: false
88
78
  version_requirements: !ruby/object:Gem::Requirement
89
- none: false
90
79
  requirements:
91
- - - ! '>='
80
+ - - '>='
92
81
  - !ruby/object:Gem::Version
93
82
  version: '0'
94
83
  - !ruby/object:Gem::Dependency
95
84
  name: ipaddress
96
85
  requirement: !ruby/object:Gem::Requirement
97
- none: false
98
86
  requirements:
99
- - - ! '>='
87
+ - - '>='
100
88
  - !ruby/object:Gem::Version
101
89
  version: '0'
102
90
  type: :runtime
103
91
  prerelease: false
104
92
  version_requirements: !ruby/object:Gem::Requirement
105
- none: false
106
93
  requirements:
107
- - - ! '>='
94
+ - - '>='
108
95
  - !ruby/object:Gem::Version
109
96
  version: '0'
110
97
  - !ruby/object:Gem::Dependency
111
98
  name: colorize
112
99
  requirement: !ruby/object:Gem::Requirement
113
- none: false
114
100
  requirements:
115
- - - ! '>='
101
+ - - '>='
116
102
  - !ruby/object:Gem::Version
117
103
  version: '0'
118
104
  type: :runtime
119
105
  prerelease: false
120
106
  version_requirements: !ruby/object:Gem::Requirement
121
- none: false
122
107
  requirements:
123
- - - ! '>='
108
+ - - '>='
124
109
  - !ruby/object:Gem::Version
125
110
  version: '0'
126
111
  description: when puppet and chef suck because you want to be in control and sprinkle
@@ -205,29 +190,37 @@ files:
205
190
  - lib/dust/recipes/zabbix_agent.rb
206
191
  - lib/dust/runner.rb
207
192
  - lib/dust/server.rb
193
+ - lib/dust/server/facter.rb
194
+ - lib/dust/server/file.rb
195
+ - lib/dust/server/osdetect.rb
196
+ - lib/dust/server/package.rb
197
+ - lib/dust/server/selinux.rb
198
+ - lib/dust/server/service.rb
199
+ - lib/dust/server/ssh.rb
200
+ - lib/dust/server/user.rb
208
201
  - lib/dust/version.rb
209
- homepage: ''
210
- licenses: []
202
+ homepage: https://github.com/kechagia/dust-deploy
203
+ licenses:
204
+ - GPLv3
205
+ metadata: {}
211
206
  post_install_message:
212
207
  rdoc_options: []
213
208
  require_paths:
214
209
  - lib
215
210
  required_ruby_version: !ruby/object:Gem::Requirement
216
- none: false
217
211
  requirements:
218
- - - ! '>='
212
+ - - '>='
219
213
  - !ruby/object:Gem::Version
220
214
  version: '0'
221
215
  required_rubygems_version: !ruby/object:Gem::Requirement
222
- none: false
223
216
  requirements:
224
- - - ! '>='
217
+ - - '>='
225
218
  - !ruby/object:Gem::Version
226
219
  version: '0'
227
220
  requirements: []
228
221
  rubyforge_project: dust-deploy
229
- rubygems_version: 1.8.23
222
+ rubygems_version: 2.0.0
230
223
  signing_key:
231
- specification_version: 3
224
+ specification_version: 4
232
225
  summary: small server deployment tool for complex environments
233
226
  test_files: []