dust-deploy 0.16.4 → 0.16.5

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.
@@ -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: []