cindy-cm 0.1.0 → 0.1.1
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.
- checksums.yaml +4 -4
- data/Changelog.md +6 -0
- data/README.md +19 -9
- data/lib/cindy/all.rb +1 -0
- data/lib/cindy/cindy.rb +77 -2
- data/lib/cindy/command.rb +4 -3
- data/lib/cindy/executor/base.rb +101 -0
- data/lib/cindy/executor/local.rb +23 -17
- data/lib/cindy/executor/ssh.rb +26 -26
- data/lib/cindy/template.rb +18 -14
- data/lib/cindy/version.rb +1 -1
- data/test/templates/deploy.tpl +0 -0
- data/test/test_cindy.rb +13 -2
- data/test/test_local_executor.rb +27 -0
- metadata +6 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f8c7c22d266d8267c749cd4c9b98eb41a52af41c
|
4
|
+
data.tar.gz: 880741fc31c94b0f89aa77d6f7c250c1310d322b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: af5a8f616854bad4d4fc7be8dcc234abbd597cda1c00e3a5e1a191ab28b7ebc9fa0fd5aadddf0753d843132f349f9741c465a5915f91d08707086ec1ea7c5d80
|
7
|
+
data.tar.gz: 928a046d7dc654ed9f9abbf18ac4677091bb50dc2b1c9d7a797161459fb6fdd336a060bdece1780dcc6fe8a5fe5a827c9bde36df6cd6e939e7162b26a11d09cd
|
data/Changelog.md
CHANGED
data/README.md
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
## Introduction
|
2
2
|
|
3
|
-
Tired to modify your configuration files depending on the targeted
|
3
|
+
Tired to modify your configuration files depending on the targeted host? Turn them out into ERB templates and deploy them in one command.
|
4
4
|
|
5
5
|
The purpose is to implement a kind of shell with limited dependencies to automate configuration and deployment on various (Unix) environments.
|
6
6
|
|
@@ -10,7 +10,7 @@ Dependencies: net-ssh, highline
|
|
10
10
|
|
11
11
|
`gem install cindy-cm`
|
12
12
|
|
13
|
-
## Usage
|
13
|
+
## Usage of cindy command
|
14
14
|
|
15
15
|
* reload => force Cindy to reload its configuration file
|
16
16
|
* environment (shortcut: env)
|
@@ -22,16 +22,18 @@ Dependencies: net-ssh, highline
|
|
22
22
|
* deploy => install the generated file on the given environment
|
23
23
|
* print => display output configuration file as it would be deployed on the given environment
|
24
24
|
* details => list all applicable variables to the given template, their values and scopes
|
25
|
+
|
25
26
|
## Example
|
26
27
|
|
27
28
|
Create ~/.cindy as follows:
|
29
|
+
|
28
30
|
```ruby
|
29
31
|
# create 2 environments named "production" and "development"
|
30
|
-
environment :development
|
32
|
+
environment :development
|
31
33
|
environment :production, 'ssh://root@www.xxx.tld/'
|
32
34
|
|
33
|
-
# register the template
|
34
|
-
template :nginx, '
|
35
|
+
# register the template ~/cindy/templates/nginx.conf.tpl (see below) for our nginx configuration
|
36
|
+
template :nginx, '~/cindy/templates/nginx.conf.tpl' do
|
35
37
|
# default variables
|
36
38
|
|
37
39
|
# have_gzip_static will be set to true or false depending on the result of the following command
|
@@ -49,10 +51,16 @@ template :nginx, '/home/julp/cindy/templates/nginx.conf.tpl' do
|
|
49
51
|
var :server_name, 'www.xxx.lan'
|
50
52
|
var :root, '/home/julp/app/public'
|
51
53
|
end
|
54
|
+
|
55
|
+
# after deployment, check syntax
|
56
|
+
postcmd 'nginx -tc $INSTALL_FILE'
|
57
|
+
# if no error, reload configuration
|
58
|
+
postcmd 'nginx -s reload'
|
52
59
|
end
|
53
60
|
```
|
54
61
|
|
55
|
-
And
|
62
|
+
And ~/cindy/templates/nginx.conf.tpl as:
|
63
|
+
|
56
64
|
```
|
57
65
|
# <%= _install_file_ %>
|
58
66
|
|
@@ -71,6 +79,7 @@ server {
|
|
71
79
|
```
|
72
80
|
|
73
81
|
Running `cindy template nginx environment production print`, result in:
|
82
|
+
|
74
83
|
```
|
75
84
|
# /usr/local/etc/nginx.conf
|
76
85
|
|
@@ -84,19 +93,20 @@ server {
|
|
84
93
|
(if we admit that nginx is built, on production environment, with Gzip Precompression module)
|
85
94
|
|
86
95
|
After `cindy template nginx environment production deploy`, output of `ls -l /etc/nginx.conf*` is:
|
96
|
+
|
87
97
|
```
|
88
98
|
lrwxrwxrwx [...] /usr/local/etc/nginx.conf -> /usr/local/etc/nginx.conf.201502262311
|
89
99
|
-rw-r--r-- [...] /usr/local/etc/nginx.conf.201502262209 # file at previous deployment
|
90
100
|
-rw-r--r-- [...] /usr/local/etc/nginx.conf.201502262311 # current version (1h02m later)
|
91
101
|
```
|
92
102
|
|
93
|
-
## What is a *
|
103
|
+
## What is a *Command* object?
|
94
104
|
|
95
105
|
It is a kind of dynamic variable: instead of hardcoding a value which depends on the remote host, we execute the associated command before each
|
96
106
|
time the template is rendered. It is more convenient mainly if this value can change at any time.
|
97
107
|
|
98
|
-
The result of the command is a boolean based on its exit status (0 => true,
|
99
|
-
standard output else a string with the content
|
108
|
+
The result of the command is a boolean based on its exit status (0 => true, everything else => false) if the command does not print anything on
|
109
|
+
standard output else a string with the content sent on standard output.
|
100
110
|
|
101
111
|
In the example above, `nginx -V 2>&1 >/dev/null | grep -qF -- --with-http_gzip_static_module` is intended to determine if nginx, on the remote
|
102
112
|
server, is compiled or not with the gzip_static module.
|
data/lib/cindy/all.rb
CHANGED
data/lib/cindy/cindy.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
require 'logger'
|
2
|
+
|
1
3
|
module Cindy
|
2
4
|
|
3
5
|
class UndefinedEnvironmentError < ::NameError
|
@@ -11,6 +13,7 @@ module Cindy
|
|
11
13
|
|
12
14
|
class Cindy
|
13
15
|
|
16
|
+
# Default location of Cindy's configuration file
|
14
17
|
CONFIGURATION_FILE = File.expand_path '~/.cindy'
|
15
18
|
|
16
19
|
module DSL
|
@@ -20,6 +23,10 @@ module Cindy
|
|
20
23
|
@envname = envname
|
21
24
|
end
|
22
25
|
|
26
|
+
def cmd(command, options = {})
|
27
|
+
Command.new command, options
|
28
|
+
end
|
29
|
+
|
23
30
|
def var(varname, value)
|
24
31
|
@tpl.set_variable @envname, varname, value
|
25
32
|
end
|
@@ -32,6 +39,10 @@ module Cindy
|
|
32
39
|
@tpl = tpl
|
33
40
|
end
|
34
41
|
|
42
|
+
def cmd(command, options = {})
|
43
|
+
Command.new command, options
|
44
|
+
end
|
45
|
+
|
35
46
|
def var(varname, value)
|
36
47
|
@tpl.set_variable nil, varname, value
|
37
48
|
end
|
@@ -42,6 +53,10 @@ module Cindy
|
|
42
53
|
@tpl.set_path_for_environment envname, file
|
43
54
|
TemplateEnvironmentNode.new(@tpl, envname).instance_eval &block if block_given?
|
44
55
|
end
|
56
|
+
|
57
|
+
def postcmd(cmd, options = {})
|
58
|
+
@tpl.add_postcmd cmd, options
|
59
|
+
end
|
45
60
|
end
|
46
61
|
|
47
62
|
class CindyNode
|
@@ -55,23 +70,40 @@ module Cindy
|
|
55
70
|
tpl
|
56
71
|
end
|
57
72
|
|
58
|
-
def environment(envname, uri =
|
73
|
+
def environment(envname, uri = '')
|
59
74
|
@cindy.environment_add envname, uri
|
60
75
|
end
|
61
76
|
end
|
62
77
|
end
|
63
78
|
|
79
|
+
# DSL
|
80
|
+
# \@!method environment(envname, uri = '')
|
81
|
+
# \@!method template(tplname, path, &block)
|
82
|
+
|
83
|
+
# @return [Logger] the logger associated to the cindy instance
|
84
|
+
attr_accessor :logger
|
85
|
+
|
86
|
+
# Creates an "empty" Cindy object
|
64
87
|
def initialize
|
65
88
|
@environments = {}
|
66
89
|
@templates = {}
|
90
|
+
@logger = Logger.new STDERR
|
67
91
|
end
|
68
92
|
|
93
|
+
# Creates a Cindy object from a string
|
94
|
+
#
|
95
|
+
# @param string [String] the configuration string to evaluate
|
96
|
+
# @return [Cindy]
|
69
97
|
def self.from_string(string)
|
70
98
|
cindy = Cindy.new
|
71
99
|
DSL::CindyNode.new(cindy).instance_eval string
|
72
100
|
cindy
|
73
101
|
end
|
74
102
|
|
103
|
+
# Creates a Cindy object from a file
|
104
|
+
#
|
105
|
+
# @param filename [String] the file to evaluate ; default location {CONFIGURATION_FILE} is used if nil
|
106
|
+
# @return [Cindy]
|
75
107
|
def self.load(filename = nil)
|
76
108
|
@filename = filename || CONFIGURATION_FILE
|
77
109
|
cindy = Cindy.new
|
@@ -83,36 +115,67 @@ module Cindy
|
|
83
115
|
(@environments.values.map(&:to_s) + [''] + @templates.values.map(&:to_s)).join("\n")
|
84
116
|
end
|
85
117
|
|
118
|
+
# Get known environments
|
119
|
+
#
|
120
|
+
# @return [Array<Symbol>] list of environment names
|
86
121
|
def environments
|
87
122
|
@environments.values
|
88
123
|
end
|
89
124
|
|
125
|
+
# Does environment exist?
|
126
|
+
#
|
127
|
+
# @param envname [String, Symbol] the name of the environment to check for existence
|
90
128
|
def has_environment?(envname)
|
91
129
|
envname = envname.intern
|
92
130
|
@environments.key? envname
|
93
131
|
end
|
94
132
|
|
133
|
+
# Add a new environment
|
134
|
+
#
|
135
|
+
# @param envname [String, Symbol] the name of the new environment
|
136
|
+
# @param attributes [String] the uri of the host (eg 'ssh://user@1.2.3.4/' for a
|
137
|
+
# remote one or 'file:///' to work directely on actual host)
|
138
|
+
# @return [Environment] the registered environment
|
95
139
|
def environment_add(envname, attributes)
|
96
140
|
envname = envname.intern
|
97
141
|
# assert !@environments.key? envname
|
98
142
|
@environments[envname] = Environment.new(envname, attributes)
|
99
143
|
end
|
100
144
|
|
145
|
+
# Get registered templates
|
146
|
+
#
|
147
|
+
# @return [Array<Symbol>] list of template names
|
101
148
|
def templates
|
102
149
|
@templates.values
|
103
150
|
end
|
104
151
|
|
152
|
+
# Does template exist?
|
153
|
+
#
|
154
|
+
# @param tplname [String, Symbol] the name of the template to check for existence
|
105
155
|
def has_template?(tplname)
|
106
156
|
tplname = tplname.intern
|
107
157
|
@templates.key? tplname
|
108
158
|
end
|
109
159
|
|
160
|
+
# Add a new template
|
161
|
+
#
|
162
|
+
# @param tplname [String, Symbol] the name of the new template
|
163
|
+
# @param file [String] the location of the template file. Note it is expanded
|
164
|
+
# ({File#expand_path}) to permit usage of ~ so it may be a good idea to avoid
|
165
|
+
# relative paths
|
166
|
+
# @return [Template] the registered template
|
110
167
|
def template_add(tplname, file)
|
111
168
|
tplname = tplname.intern
|
112
169
|
# assert !@templates.key? name
|
113
|
-
@templates[tplname] = Template.new File.expand_path(file), tplname
|
170
|
+
@templates[tplname] = Template.new self, File.expand_path(file), tplname
|
114
171
|
end
|
115
172
|
|
173
|
+
# Print on stdout the result of template generation
|
174
|
+
#
|
175
|
+
# @param envname [String, Symbol] the name of the environment
|
176
|
+
# @param tplname [String, Symbol] the name of the template
|
177
|
+
# @raise [UndefinedEnvironmentError] if environment does not exist
|
178
|
+
# @raise [UndefinedTemplateError] if template does not exist
|
116
179
|
def template_environment_print(envname, tplname)
|
117
180
|
envname = envname.intern
|
118
181
|
tplname = tplname.intern
|
@@ -121,6 +184,12 @@ module Cindy
|
|
121
184
|
@templates[tplname].print @environments[envname]
|
122
185
|
end
|
123
186
|
|
187
|
+
# Deploy the result of template generation on the given environment
|
188
|
+
#
|
189
|
+
# @param envname [String, Symbol] the name of the environment
|
190
|
+
# @param tplname [String, Symbol] the name of the template
|
191
|
+
# @raise [UndefinedEnvironmentError] if environment does not exist
|
192
|
+
# @raise [UndefinedTemplateError] if template does not exist
|
124
193
|
def template_environment_deploy(envname, tplname)
|
125
194
|
envname = envname.intern
|
126
195
|
tplname = tplname.intern
|
@@ -129,6 +198,12 @@ module Cindy
|
|
129
198
|
@templates[tplname].deploy @environments[envname]
|
130
199
|
end
|
131
200
|
|
201
|
+
# Print on stdout a detail (value and scope) of applicable variables
|
202
|
+
#
|
203
|
+
# @param envname [String, Symbol] the name of the environment
|
204
|
+
# @param tplname [String, Symbol] the name of the template
|
205
|
+
# @raise [UndefinedEnvironmentError] if environment does not exist
|
206
|
+
# @raise [UndefinedTemplateError] if template does not exist
|
132
207
|
def template_environment_variables(envname, tplname)
|
133
208
|
envname = envname.intern
|
134
209
|
tplname = tplname.intern
|
data/lib/cindy/command.rb
CHANGED
@@ -1,7 +1,8 @@
|
|
1
1
|
module Cindy
|
2
2
|
class Command
|
3
|
-
def initialize(command)
|
3
|
+
def initialize(command, options = {})
|
4
4
|
@command = command
|
5
|
+
@options = options
|
5
6
|
end
|
6
7
|
|
7
8
|
def to_s
|
@@ -9,11 +10,11 @@ module Cindy
|
|
9
10
|
end
|
10
11
|
|
11
12
|
def inspect
|
12
|
-
"#{self.class.name}.new(#{@command.inspect})"
|
13
|
+
"#{self.class.name}.new(#{@command.inspect}, TODO)"
|
13
14
|
end
|
14
15
|
|
15
16
|
def call(executor)
|
16
|
-
executor.exec(@command,
|
17
|
+
executor.exec(@command, @options)
|
17
18
|
end
|
18
19
|
end
|
19
20
|
end
|
@@ -0,0 +1,101 @@
|
|
1
|
+
require 'uri'
|
2
|
+
|
3
|
+
module Cindy
|
4
|
+
module Executor
|
5
|
+
class CommandFailedError < StandardError
|
6
|
+
end
|
7
|
+
|
8
|
+
class Base
|
9
|
+
# X
|
10
|
+
#
|
11
|
+
# @param [Logger] logger
|
12
|
+
def initialize(logger)
|
13
|
+
@logger = logger
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.from_uri(uri, logger)
|
17
|
+
uri = URI.parse(uri)
|
18
|
+
ObjectSpace.each_object(Class).select { |v| return v.new(uri, logger) if v.ancestors.include?(self) && v.handle?(uri) }
|
19
|
+
raise Exception.new 'Unexpected protocol'
|
20
|
+
end
|
21
|
+
|
22
|
+
# Executes the given command
|
23
|
+
#
|
24
|
+
# @param command [String] the command to execute
|
25
|
+
# @param options [Hash] the options to execute the command with
|
26
|
+
# @option options [String] :stdin the string to send as/on stdin
|
27
|
+
# @option options [Boolean] :ignore_failure don't raise an exception if true when the command returns a non 0 exit value
|
28
|
+
# @option options [Hash] :env the environment variables to set/pass for command execution
|
29
|
+
# @option options [Boolean] :check_status_only simply return true if the command is successful else false
|
30
|
+
# @return [String, Boolean]
|
31
|
+
def exec(command, options = {})
|
32
|
+
=begin
|
33
|
+
stdout, stderr, status = exec_imp command, options[:stdin]
|
34
|
+
stdout.chomp!
|
35
|
+
# <logging>
|
36
|
+
if status.zero?
|
37
|
+
if stdout.empty?
|
38
|
+
@logger.info 'Command "%s" executed successfully' % command
|
39
|
+
else
|
40
|
+
@logger.info 'Command "%s" executed successfully (with "%s" returned)' % [ command, stdout ]
|
41
|
+
end
|
42
|
+
else
|
43
|
+
@logger.send(options[:ignore_failure] ? :warn : :error, 'Command "%s" failed with "%s"' % [ command, stderr ])
|
44
|
+
end
|
45
|
+
# </logging>
|
46
|
+
return status.zero? if options[:check_status_only]
|
47
|
+
raise CommandFailedError.new "Command '#{command}' failed" if !options[:ignore_failure] && 0 != status
|
48
|
+
=end
|
49
|
+
stdout, stderr, status = exec_and_log command, options
|
50
|
+
# ?!?
|
51
|
+
stdout
|
52
|
+
end
|
53
|
+
|
54
|
+
def exec!(command, options = {})
|
55
|
+
stdout, stderr, status = exec_and_log command, options
|
56
|
+
raise CommandFailedError.new "Command '#{command}' failed" unless 0 == status
|
57
|
+
stdout
|
58
|
+
end
|
59
|
+
|
60
|
+
# Close the eventual underlaying connection
|
61
|
+
def close
|
62
|
+
# NOP
|
63
|
+
end
|
64
|
+
|
65
|
+
private
|
66
|
+
|
67
|
+
def exec_and_log(command, options = {})
|
68
|
+
stdout, stderr, status = exec_imp command, options[:stdin]
|
69
|
+
stdout.chomp!
|
70
|
+
# <logging>
|
71
|
+
if status.zero?
|
72
|
+
if stdout.empty?
|
73
|
+
@logger.info 'Command "%s" executed successfully' % command
|
74
|
+
else
|
75
|
+
@logger.info 'Command "%s" executed successfully (with "%s" returned)' % [ command, stdout ]
|
76
|
+
end
|
77
|
+
else
|
78
|
+
@logger.send(options[:ignore_failure] ? :warn : :error, 'Command "%s" failed with "%s"' % [ command, stderr ])
|
79
|
+
end
|
80
|
+
[ stdout, stderr, status ]
|
81
|
+
end
|
82
|
+
|
83
|
+
protected
|
84
|
+
|
85
|
+
# @abstract
|
86
|
+
def self.handle?(uri)
|
87
|
+
false
|
88
|
+
end
|
89
|
+
|
90
|
+
# @abstract
|
91
|
+
# @param command [String] the command to execute
|
92
|
+
# @return [Array<(String, String, Fixnum)>] an array containing the following elements (in this order):
|
93
|
+
# 1. output on stdout
|
94
|
+
# 2. output on stderr
|
95
|
+
# 3. exit status
|
96
|
+
def exec_imp(command, stdin_str)
|
97
|
+
raise NotImplementedError.new
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
data/lib/cindy/executor/local.rb
CHANGED
@@ -1,28 +1,34 @@
|
|
1
|
+
require 'uri'
|
1
2
|
require 'open3'
|
2
3
|
|
3
4
|
module Cindy
|
4
5
|
module Executor
|
5
|
-
class Local
|
6
|
-
def
|
6
|
+
class Local < Base
|
7
|
+
def self.handle?(uri)
|
8
|
+
[ nil, 'file' ].include? uri.scheme
|
9
|
+
end
|
10
|
+
|
11
|
+
def initialize(uri, logger)
|
12
|
+
super logger
|
13
|
+
end
|
14
|
+
|
15
|
+
def exec_imp(command, stdin_str)
|
7
16
|
exit_status = 1
|
8
17
|
stdout_str = stderr_str = ''
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
18
|
+
begin
|
19
|
+
Open3.popen3({ 'PATH' => "#{ENV['PATH']}:/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin" }, command) do |stdin, stdout, stderr, wait_thr|
|
20
|
+
if stdin_str
|
21
|
+
stdin.write stdin_str
|
22
|
+
stdin.close
|
23
|
+
end
|
24
|
+
stdout_str = stdout.read
|
25
|
+
stderr_str = stderr.read
|
26
|
+
exit_status = wait_thr.value.exitstatus
|
13
27
|
end
|
14
|
-
|
15
|
-
stderr_str =
|
16
|
-
exit_status = wait_thr.value
|
28
|
+
rescue Errno::ENOENT => e
|
29
|
+
stderr_str = e.message
|
17
30
|
end
|
18
|
-
|
19
|
-
raise Exception.new if 0 != exit_status && !stderr_str.empty?
|
20
|
-
return nil if status_only && 0 != exit_status
|
21
|
-
stdout_str.chomp
|
22
|
-
end
|
23
|
-
|
24
|
-
def close
|
25
|
-
# NOP
|
31
|
+
[ stdout_str, stderr_str, exit_status ]
|
26
32
|
end
|
27
33
|
end
|
28
34
|
end
|
data/lib/cindy/executor/ssh.rb
CHANGED
@@ -1,38 +1,38 @@
|
|
1
|
+
require 'uri'
|
1
2
|
require 'net/ssh'
|
2
3
|
|
3
4
|
module Cindy
|
4
5
|
module Executor
|
5
|
-
class SSH
|
6
|
-
def
|
7
|
-
|
6
|
+
class SSH < Base
|
7
|
+
def self.handle?(uri)
|
8
|
+
'ssh' == uri.scheme
|
8
9
|
end
|
9
10
|
|
10
|
-
def
|
11
|
+
def initialize(uri, logger)
|
12
|
+
@cnx = Net::SSH.start(uri.host, uri.user)
|
13
|
+
super logger
|
14
|
+
end
|
15
|
+
|
16
|
+
def exec_imp(command, stdin_str)
|
11
17
|
exit_status = 1
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
channel.
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
exit_status = data.read_long
|
24
|
-
end
|
25
|
-
channel.send_data stdin_str if stdin_str
|
26
|
-
channel.eof!
|
18
|
+
stdout_str = stderr_str = ''
|
19
|
+
@cnx.open_channel do |channel|
|
20
|
+
channel.exec(command) do |ch, success|
|
21
|
+
channel.on_data do |ch, data|
|
22
|
+
stdout_str += data
|
23
|
+
end
|
24
|
+
channel.on_extended_data do |ch, type, data|
|
25
|
+
stderr_str += data
|
26
|
+
end
|
27
|
+
channel.on_request 'exit-status' do |ch, data|
|
28
|
+
exit_status = data.read_long
|
27
29
|
end
|
30
|
+
channel.send_data stdin_str.force_encoding('ASCII-8BIT') if stdin_str
|
31
|
+
channel.eof!
|
28
32
|
end
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
# result.chomp if result.respond_to? :chomp # as result can be nil <=> result.chomp if result <=> result.try? :chomp (rails way)
|
33
|
-
# end
|
34
|
-
return nil if status_only && 0 != exit_status
|
35
|
-
stdout_str.chomp
|
33
|
+
end
|
34
|
+
@cnx.loop
|
35
|
+
[ stdout_str, stderr_str, exit_status ]
|
36
36
|
end
|
37
37
|
|
38
38
|
def close
|
data/lib/cindy/template.rb
CHANGED
@@ -1,18 +1,20 @@
|
|
1
1
|
require 'erb'
|
2
|
-
require 'uri'
|
3
2
|
require 'ostruct'
|
3
|
+
require 'shellwords'
|
4
4
|
|
5
5
|
module Cindy
|
6
6
|
class Template
|
7
7
|
|
8
|
-
attr_reader :file, :alias, :paths, :defvars, :envvars
|
8
|
+
attr_reader :file, :alias, :paths, :defvars, :envvars, :postcmds
|
9
9
|
|
10
|
-
def initialize(file, name)
|
10
|
+
def initialize(owner, file, name)
|
11
|
+
@owner = owner # owner is Cindy objet
|
11
12
|
@file = file # local template filename
|
12
13
|
@alias = name
|
13
14
|
@paths = {} # remote filenames (<environment name> => <filename>)
|
14
15
|
@defvars = {} # default/global variables
|
15
16
|
@envvars = {} # environment specific variables
|
17
|
+
@postcmds = [] # commands to run after deployment
|
16
18
|
end
|
17
19
|
|
18
20
|
IDENT_STRING = ' ' * 4
|
@@ -38,15 +40,25 @@ module Cindy
|
|
38
40
|
puts render(env)
|
39
41
|
end
|
40
42
|
|
43
|
+
def add_postcmd(cmd, options)
|
44
|
+
@postcmds << cmd
|
45
|
+
end
|
46
|
+
|
41
47
|
def deploy(env)
|
42
48
|
executor = executor_for_env env
|
43
49
|
remote_filename = @paths[env.name]
|
44
50
|
sudo = ''
|
45
|
-
sudo = 'sudo
|
51
|
+
sudo = 'sudo' unless 0 == executor.exec('id -u').to_i
|
46
52
|
suffix = executor.exec('date \'+%Y%m%d%H%M\'') # use remote - not local - time machine
|
47
53
|
executor.exec("[ -e \"#{remote_filename}\" ] && [ ! -h \"#{remote_filename}\" ] && #{sudo} mv -i \"#{remote_filename}\" \"#{remote_filename}.pre\"")
|
48
|
-
executor.exec("#{sudo} tee #{remote_filename}.#{suffix} > /dev/null", render(env, executor))
|
54
|
+
executor.exec("#{sudo} tee #{remote_filename}.#{suffix} > /dev/null", stdin: render(env, executor))
|
49
55
|
executor.exec("#{sudo} ln -snf \"#{remote_filename}.#{suffix}\" \"#{remote_filename}\"")
|
56
|
+
shell = executor.exec('ps -p $$ -ocomm=')
|
57
|
+
env = { 'INSTALL_FILE' => remote_filename }
|
58
|
+
env_string = env.inject([]) { |a, b| a << b.map(&:shellescape).join('=') }.join(' ')
|
59
|
+
@postcmds.each do |cmd|
|
60
|
+
executor.exec("#{sudo} #{'env' if shell =~ /csh\z/} #{env_string} sh -c '#{cmd}'") # TODO: escape single quotes in cmd?
|
61
|
+
end
|
50
62
|
executor.close
|
51
63
|
end
|
52
64
|
|
@@ -94,15 +106,7 @@ module Cindy
|
|
94
106
|
private
|
95
107
|
|
96
108
|
def executor_for_env(env)
|
97
|
-
|
98
|
-
case uri.scheme
|
99
|
-
when nil, 'file'
|
100
|
-
Executor::Local.new
|
101
|
-
when 'ssh'
|
102
|
-
Executor::SSH.new Net::SSH.start(uri.host, uri.user)
|
103
|
-
else
|
104
|
-
raise Exception.new 'Unexpected protocol'
|
105
|
-
end
|
109
|
+
Executor::Base.from_uri env.uri, @owner.logger
|
106
110
|
end
|
107
111
|
|
108
112
|
def render(env, executor = nil)
|
data/lib/cindy/version.rb
CHANGED
File without changes
|
data/test/test_cindy.rb
CHANGED
@@ -1,9 +1,20 @@
|
|
1
|
+
require 'logger'
|
1
2
|
require 'minitest_helper'
|
2
3
|
|
3
4
|
class CindyTest < Minitest::Test
|
4
5
|
|
5
|
-
def
|
6
|
-
|
6
|
+
def test_deploy
|
7
|
+
cindy = Cindy::Cindy.from_string <<-EOS
|
8
|
+
environment :foo, 'file:///'
|
9
|
+
|
10
|
+
template :bar, '#{File.join(__dir__, 'templates', 'deploy.tpl')}' do
|
11
|
+
on :foo, '/tmp/#{$$}'
|
12
|
+
end
|
13
|
+
EOS
|
14
|
+
cindy.logger = Logger.new File.open('/dev/null', File::WRONLY)
|
15
|
+
|
16
|
+
cindy.template_environment_deploy :foo, :bar
|
17
|
+
assert File.symlink?("/tmp/#{$$}")
|
7
18
|
end
|
8
19
|
|
9
20
|
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require 'logger'
|
2
|
+
require 'minitest_helper'
|
3
|
+
|
4
|
+
class LocalExecutorTest < Minitest::Test
|
5
|
+
|
6
|
+
def test_local_executor
|
7
|
+
logger = Logger.new File.open('/dev/null', File::WRONLY)
|
8
|
+
locexec = Cindy::Executor::Local.new nil, logger
|
9
|
+
# unknown command
|
10
|
+
assert_raises Cindy::Executor::CommandFailedError do
|
11
|
+
locexec.exec "eko foo"
|
12
|
+
end
|
13
|
+
assert_equal '', locexec.exec("eko foo", ignore_failure: true)
|
14
|
+
assert !locexec.exec("eko foo", ignore_failure: true, check_status_only: true)
|
15
|
+
# command failure
|
16
|
+
assert_raises Cindy::Executor::CommandFailedError do
|
17
|
+
locexec.exec 'grep -q foo', stdin: 'bar'
|
18
|
+
end
|
19
|
+
assert_equal '', locexec.exec("grep -q foo", stdin: 'bar', ignore_failure: true)
|
20
|
+
assert !locexec.exec("grep -q foo", stdin: 'bar', ignore_failure: true, check_status_only: true)
|
21
|
+
# valid command
|
22
|
+
assert_equal 'foo', locexec.exec('echo foo')
|
23
|
+
assert_equal 'foo', locexec.exec('echo foo', ignore_failure: true)
|
24
|
+
assert locexec.exec('echo foo', ignore_failure: true, check_status_only: true)
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: cindy-cm
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- julp
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2015-
|
11
|
+
date: 2015-09-08 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: net-ssh
|
@@ -84,14 +84,17 @@ files:
|
|
84
84
|
- lib/cindy/cli.rb
|
85
85
|
- lib/cindy/command.rb
|
86
86
|
- lib/cindy/environment.rb
|
87
|
+
- lib/cindy/executor/base.rb
|
87
88
|
- lib/cindy/executor/local.rb
|
88
89
|
- lib/cindy/executor/ssh.rb
|
89
90
|
- lib/cindy/template.rb
|
90
91
|
- lib/cindy/variable.rb
|
91
92
|
- lib/cindy/version.rb
|
92
93
|
- test/minitest_helper.rb
|
94
|
+
- test/templates/deploy.tpl
|
93
95
|
- test/test_cindy.rb
|
94
96
|
- test/test_dsl.rb
|
97
|
+
- test/test_local_executor.rb
|
95
98
|
homepage: https://github.com/julp/cindy
|
96
99
|
licenses:
|
97
100
|
- BSD
|
@@ -112,7 +115,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
112
115
|
version: '0'
|
113
116
|
requirements: []
|
114
117
|
rubyforge_project:
|
115
|
-
rubygems_version: 2.4.
|
118
|
+
rubygems_version: 2.4.8
|
116
119
|
signing_key:
|
117
120
|
specification_version: 4
|
118
121
|
summary: Turn out your configuration files into ERB templates and deploy them
|