cindy-cm 0.1.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.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 774f6b5df9922b3a6771968f4fb61e05d8b0f3dc
4
+ data.tar.gz: 647e09f6a7ebb222966f785c49492a525fd92c5f
5
+ SHA512:
6
+ metadata.gz: 8ecafc4acc58778989dbab0e650d18a70e026ccf985593ca1c1d5adf2003da8c804b90e5bfae9bad47571a90e9260335046f1a264c208fb0c8028052c411f309
7
+ data.tar.gz: 2a51c783e0ab07d5b95e0448c5b8e7bf6737cc7804dfc3935ef56fe629c29b23b7bab67d55a1c3d7c946c805dc5c563a7acefbe3566f0c85f56257cdd7f534a8
@@ -0,0 +1,7 @@
1
+ ### Changes from 0.0.1 to 0.1.0
2
+
3
+ * configuration format changed (XML => Ruby)
4
+ * shell completion added
5
+ * reload command added
6
+ * alternate configuration file can be specified through environment variable CINDY_CONF (eg, with (ba|k|z)sh: `CINDY_CONF=alternate/path cindy`)
7
+ * all commands which change internal state removed
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in cindy.gemspec
4
+ gemspec
@@ -0,0 +1,121 @@
1
+ ## Introduction
2
+
3
+ Tired to modify your configuration files depending on the targeted computer? Turn them out into ERB templates and deploy them in one command.
4
+
5
+ The purpose is to implement a kind of shell with limited dependencies to automate configuration and deployment on various (Unix) environments.
6
+
7
+ Dependencies: net-ssh, highline
8
+
9
+ ## Installation
10
+
11
+ `gem install cindy-cm`
12
+
13
+ ## Usage
14
+
15
+ * reload => force Cindy to reload its configuration file
16
+ * environment (shortcut: env)
17
+ * list => list all known environments
18
+ * template (shortcut: tpl)
19
+ * list => list all known templates
20
+ * \<name>
21
+ * environment \<name>
22
+ * deploy => install the generated file on the given environment
23
+ * print => display output configuration file as it would be deployed on the given environment
24
+ * details => list all applicable variables to the given template, their values and scopes
25
+ ## Example
26
+
27
+ Create ~/.cindy as follows:
28
+ ```ruby
29
+ # create 2 environments named "production" and "development"
30
+ environment :development, 'file:///'
31
+ environment :production, 'ssh://root@www.xxx.tld/'
32
+
33
+ # register the template /home/julp/cindy/templates/nginx.conf.tpl (see below) for our nginx configuration
34
+ template :nginx, '/home/julp/cindy/templates/nginx.conf.tpl' do
35
+ # default variables
36
+
37
+ # have_gzip_static will be set to true or false depending on the result of the following command
38
+ var :have_gzip_static, Command.new('nginx -V 2>&1 >/dev/null | grep -qF -- --with-http_gzip_static_module') # (ba|k)sh
39
+
40
+ on :production, '/usr/local/etc/nginx.conf' do
41
+ # define and/or override some variables for nginx when on our production environment
42
+ var :server_name, 'www.xxx.tld'
43
+ var :root, '/home/julp/app/current/public'
44
+ var :have_gzip_static, Command.new('(nginx -V > /dev/null) |& grep -qF -- --with-http_gzip_static_module') # (t)csh
45
+ end
46
+
47
+ on :development, '/etc/nginx.conf' do
48
+ # (re)define variables for nginx when in development
49
+ var :server_name, 'www.xxx.lan'
50
+ var :root, '/home/julp/app/public'
51
+ end
52
+ end
53
+ ```
54
+
55
+ And /home/julp/cindy/templates/nginx.conf.tpl as:
56
+ ```
57
+ # <%= _install_file_ %>
58
+
59
+ server {
60
+ server_name <%= server_name %>;
61
+ root <%= root %>;
62
+
63
+ <% if false %>
64
+ this content never appears
65
+ <% end %>
66
+
67
+ <% if have_gzip_static %>
68
+ gzip_static on;
69
+ <% end %>
70
+ }
71
+ ```
72
+
73
+ Running `cindy template nginx environment production print`, result in:
74
+ ```
75
+ # /usr/local/etc/nginx.conf
76
+
77
+ server {
78
+ server_name www.xxx.tld;
79
+ root /home/julp/app/current/public;
80
+
81
+ gzip_static on;
82
+ }
83
+ ```
84
+ (if we admit that nginx is built, on production environment, with Gzip Precompression module)
85
+
86
+ After `cindy template nginx environment production deploy`, output of `ls -l /etc/nginx.conf*` is:
87
+ ```
88
+ lrwxrwxrwx [...] /usr/local/etc/nginx.conf -> /usr/local/etc/nginx.conf.201502262311
89
+ -rw-r--r-- [...] /usr/local/etc/nginx.conf.201502262209 # file at previous deployment
90
+ -rw-r--r-- [...] /usr/local/etc/nginx.conf.201502262311 # current version (1h02m later)
91
+ ```
92
+
93
+ ## What is a *command* typed variable?
94
+
95
+ 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
+ time the template is rendered. It is more convenient mainly if this value can change at any time.
97
+
98
+ The result of the command is a boolean based on its exit status (0 => true, everithing else => false) if the command does not print anything on
99
+ standard output else a string with the content of standard output.
100
+
101
+ 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
+ server, is compiled or not with the gzip_static module.
103
+
104
+ As you can see in this same example, note that commands may depend on the "remote shell" (redirections in particular) and also on the value of the
105
+ PATH environment variable.
106
+
107
+ ## Predefined variables in templates
108
+
109
+ * `_install_dir_`: directory in which output file will be deployed (equivalent to `File.dirname _install_file_` but more convenient)
110
+ * `_install_file_`: filename under which the file will be deployed
111
+
112
+ ## Limitations
113
+
114
+ * `sudo` prompt to ask passwords is not handled: use a passwordless configuration
115
+ * you need to configure your ssh keys in `~/.ssh/config`, eg:
116
+
117
+ ```
118
+ Host www.domain.tld
119
+ User julp
120
+ IdentityFile /path/to/your/private/key
121
+ ```
@@ -0,0 +1,9 @@
1
+ require 'bundler/gem_tasks'
2
+ require 'rake/testtask'
3
+
4
+ Rake::TestTask.new do |t|
5
+ t.libs << 'test'
6
+ end
7
+
8
+ desc "Run tests"
9
+ task :default => :test
@@ -0,0 +1,54 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'cindy/all'
4
+ require 'cindy/cli'
5
+
6
+ require 'readline'
7
+ require 'shellwords'
8
+
9
+ cli = Cindy::CLI.new
10
+
11
+ Readline.completion_append_character = " "
12
+ Readline.completion_proc = proc do |s|
13
+ args = Shellwords.split Readline.line_buffer[0, Readline.point]
14
+ args.pop unless Readline.line_buffer.end_with? Readline.completion_append_character
15
+ (
16
+ case args
17
+ when []
18
+ %w(reload environment template)
19
+ when %w(environment), %w(env)
20
+ %w(list)
21
+ when %w(template), %w(tpl)
22
+ %w(list) + cli.templates
23
+ else
24
+ if 'template' == Cindy::CLI::ARGS_ALIASES[args[0]] && args.length >= 2
25
+ tplname = args.delete_at 1
26
+ if 1 == args.length
27
+ %w(environment) unless 'list' == tplname
28
+ elsif 'environment' == Cindy::CLI::ARGS_ALIASES[args[1]]
29
+ case args.length
30
+ when 2
31
+ cli.environments
32
+ when 3
33
+ %w(deploy print details)
34
+ end
35
+ end
36
+ end
37
+ end || []
38
+ ).select { |v| v.start_with? s }
39
+ end
40
+
41
+ CINDY_EXCEPTIONS = ObjectSpace.each_object(Class).select { |v| v.ancestors.include?(Exception) && 'Cindy' == v.name.split('::').first }
42
+
43
+ if ARGV.any?
44
+ cli.parse ARGV
45
+ else
46
+ while line = Readline.readline('# ', true)
47
+ begin
48
+ cli.parse Shellwords.split(line)
49
+ rescue => e
50
+ raise unless CINDY_EXCEPTIONS.include? e.class
51
+ puts "[ ERR ] #{e}"
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,22 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'cindy/version'
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = 'cindy-cm'
8
+ s.version = Cindy::VERSION
9
+ s.authors = ['julp']
10
+ s.summary = 'Turn out your configuration files into ERB templates and deploy them'
11
+ s.homepage = 'https://github.com/julp/cindy'
12
+ s.license = 'BSD'
13
+ s.files = `git ls-files -z`.split("\x0")
14
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
15
+ s.executables = s.files.grep(%r{^bin/}) { |f| File.basename(f) }
16
+ #s.require_paths = %w(lib)
17
+ s.required_ruby_version = '>= 2.0.0'
18
+ s.add_dependency 'net-ssh'
19
+ s.add_dependency 'highline'
20
+ s.add_development_dependency 'bundler'
21
+ s.add_development_dependency 'rake'
22
+ end
@@ -0,0 +1,11 @@
1
+ require 'cindy/version'
2
+ require 'cindy/cindy'
3
+ require 'cindy/command'
4
+ require 'cindy/environment'
5
+ require 'cindy/variable'
6
+ require 'cindy/template'
7
+ require 'cindy/executor/ssh'
8
+ require 'cindy/executor/local'
9
+
10
+ module Cindy
11
+ end
@@ -0,0 +1,151 @@
1
+ module Cindy
2
+
3
+ class UndefinedEnvironmentError < ::NameError
4
+ end
5
+
6
+ class UndefinedTemplateError < ::NameError
7
+ end
8
+
9
+ class AlreadyExistsError < ::NameError
10
+ end
11
+
12
+ class Cindy
13
+
14
+ CONFIGURATION_FILE = File.expand_path '~/.cindy'
15
+
16
+ module DSL
17
+ class TemplateEnvironmentNode
18
+ def initialize(tpl, envname)
19
+ @tpl = tpl
20
+ @envname = envname
21
+ end
22
+
23
+ def var(varname, value)
24
+ @tpl.set_variable @envname, varname, value
25
+ end
26
+
27
+ alias_method :variable, :var
28
+ end
29
+
30
+ class TemplateNode
31
+ def initialize(tpl)
32
+ @tpl = tpl
33
+ end
34
+
35
+ def var(varname, value)
36
+ @tpl.set_variable nil, varname, value
37
+ end
38
+
39
+ alias_method :variable, :var
40
+
41
+ def on(envname, file, &block)
42
+ @tpl.set_path_for_environment envname, file
43
+ TemplateEnvironmentNode.new(@tpl, envname).instance_eval &block if block_given?
44
+ end
45
+ end
46
+
47
+ class CindyNode
48
+ def initialize(cindy)
49
+ @cindy = cindy
50
+ end
51
+
52
+ def template(tplname, path, &block)
53
+ tpl = @cindy.template_add tplname, path
54
+ TemplateNode.new(tpl).instance_eval &block
55
+ tpl
56
+ end
57
+
58
+ def environment(envname, uri = nil)
59
+ @cindy.environment_add envname, uri
60
+ end
61
+ end
62
+ end
63
+
64
+ def initialize
65
+ @environments = {}
66
+ @templates = {}
67
+ end
68
+
69
+ def self.from_string(string)
70
+ cindy = Cindy.new
71
+ DSL::CindyNode.new(cindy).instance_eval string
72
+ cindy
73
+ end
74
+
75
+ def self.load(filename = nil)
76
+ @filename = filename || CONFIGURATION_FILE
77
+ cindy = Cindy.new
78
+ DSL::CindyNode.new(cindy).instance_eval(File.read(@filename), File.basename(@filename), 0)
79
+ cindy
80
+ end
81
+
82
+ def to_s
83
+ (@environments.values.map(&:to_s) + [''] + @templates.values.map(&:to_s)).join("\n")
84
+ end
85
+
86
+ def environments
87
+ @environments.values
88
+ end
89
+
90
+ def has_environment?(envname)
91
+ envname = envname.intern
92
+ @environments.key? envname
93
+ end
94
+
95
+ def environment_add(envname, attributes)
96
+ envname = envname.intern
97
+ # assert !@environments.key? envname
98
+ @environments[envname] = Environment.new(envname, attributes)
99
+ end
100
+
101
+ def templates
102
+ @templates.values
103
+ end
104
+
105
+ def has_template?(tplname)
106
+ tplname = tplname.intern
107
+ @templates.key? tplname
108
+ end
109
+
110
+ def template_add(tplname, file)
111
+ tplname = tplname.intern
112
+ # assert !@templates.key? name
113
+ @templates[tplname] = Template.new File.expand_path(file), tplname
114
+ end
115
+
116
+ def template_environment_print(envname, tplname)
117
+ envname = envname.intern
118
+ tplname = tplname.intern
119
+ check_environment! envname
120
+ check_template! tplname
121
+ @templates[tplname].print @environments[envname]
122
+ end
123
+
124
+ def template_environment_deploy(envname, tplname)
125
+ envname = envname.intern
126
+ tplname = tplname.intern
127
+ check_environment! envname
128
+ check_template! tplname
129
+ @templates[tplname].deploy @environments[envname]
130
+ end
131
+
132
+ def template_environment_variables(envname, tplname)
133
+ envname = envname.intern
134
+ tplname = tplname.intern
135
+ check_environment! envname
136
+ check_template! tplname
137
+ @templates[tplname].list_variables envname
138
+ end
139
+
140
+ private
141
+
142
+ def check_environment!(envname)
143
+ raise UndefinedEnvironmentError.new "call to an undefined environment: #{envname}" unless has_environment? envname
144
+ end
145
+
146
+ def check_template!(tplname)
147
+ raise UndefinedTemplateError.new "call to an undefined template: #{tplname}" unless has_template? tplname
148
+ end
149
+
150
+ end
151
+ end
@@ -0,0 +1,96 @@
1
+ module Cindy
2
+ class CLI
3
+ ARGS_ALIASES = Hash.new do |h,k|
4
+ k
5
+ end.merge!({ 'tpl' => 'template', 'var' => 'variable', 'env' => 'environment' })
6
+
7
+ class InvalidArgumentError < ::ArgumentError
8
+ def initialize(given, expected)
9
+ super "invalid argument '#{given}'" + case expected
10
+ when String
11
+ ", expecting '#{expected}'"
12
+ when Array
13
+ ", expecting one of: '#{expected.join('\', \'')}'"
14
+ end
15
+ end
16
+ end
17
+
18
+ # class TooManyArgumentError < ::ArgumentError
19
+ # def initialize
20
+ # super "too many arguments"
21
+ # end
22
+ # end
23
+ #
24
+ # class TooFewArgumentError < ::ArgumentError
25
+ # def initialize
26
+ # super "too few arguments"
27
+ # end
28
+ # end
29
+
30
+ def initialize
31
+ @cindy = Cindy.load ENV['CINDY_CONF']
32
+ end
33
+
34
+ def environments
35
+ @cindy.environments.map { |v| v.name.to_s }
36
+ end
37
+
38
+ def templates
39
+ @cindy.templates.map { |v| v.alias.to_s }
40
+ end
41
+
42
+ # def check_args_count(given, expected, method = :"==")
43
+ # raise (given > expected ? TooManyArgumentError : TooFewArgumentError).new unless given.send(method, expected)
44
+ # end
45
+
46
+ def parse(args)
47
+ arg = args.shift
48
+ case ARGS_ALIASES[arg]
49
+ when 'reload'
50
+ # assert 0 == args.length
51
+ @cindy = Cindy.load ENV['CINDY_CONF']
52
+ when 'environment'
53
+ arg = args.shift
54
+ case arg
55
+ when 'list'
56
+ # assert 0 == args.length
57
+ @cindy.environments.each do |env|
58
+ puts "- #{env.name}: #{env.uri}"
59
+ end
60
+ end
61
+ when 'template'
62
+ arg = args.shift
63
+ case arg
64
+ when 'list'
65
+ # assert 0 == args.length
66
+ @cindy.templates.each do |tpl|
67
+ puts "> #{tpl.alias}: #{tpl.file}"
68
+ end
69
+ else
70
+ tplname = arg
71
+ arg = args.shift
72
+ case ARGS_ALIASES[arg]
73
+ when 'environment'
74
+ # assert args.length >= 2
75
+ envname = args.shift
76
+ arg = args.shift
77
+ case ARGS_ALIASES[arg]
78
+ when 'details'
79
+ # assert 0 == args.length
80
+ @cindy.template_environment_variables(envname, tplname)
81
+ when 'deploy', 'print'
82
+ # assert 0 == args.length
83
+ @cindy.send(:"template_environment_#{arg}", envname, tplname)
84
+ else
85
+ raise InvalidArgumentError.new arg, %w(details deploy print)
86
+ end
87
+ else
88
+ raise InvalidArgumentError.new arg, %w(environment)
89
+ end
90
+ end
91
+ else
92
+ raise InvalidArgumentError.new arg, %w(reload environment template)
93
+ end
94
+ end
95
+ end
96
+ end
@@ -0,0 +1,19 @@
1
+ module Cindy
2
+ class Command
3
+ def initialize(command)
4
+ @command = command
5
+ end
6
+
7
+ def to_s
8
+ @command
9
+ end
10
+
11
+ def inspect
12
+ "#{self.class.name}.new(#{@command.inspect})"
13
+ end
14
+
15
+ def call(executor)
16
+ executor.exec(@command, nil, true)
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,20 @@
1
+ module Cindy
2
+ class Environment
3
+
4
+ attr_reader :name, :uri
5
+
6
+ def initialize(name, uri)
7
+ @uri = uri
8
+ @name = name
9
+ end
10
+
11
+ def to_s
12
+ "environment :#{@name}, #{@uri.inspect}"
13
+ end
14
+
15
+ def update(attributes)
16
+ @uri = attributes['uri'] if attributes['uri']
17
+ @name = attributes['name'] if attributes['name']
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,29 @@
1
+ require 'open3'
2
+
3
+ module Cindy
4
+ module Executor
5
+ class Local
6
+ def exec(command, stdin_str = nil, status_only = false)
7
+ exit_status = 1
8
+ stdout_str = stderr_str = ''
9
+ Open3.popen3({ 'PATH' => "#{ENV['PATH']}:/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin" }, command) do |stdin, stdout, stderr, wait_thr|
10
+ if stdin_str
11
+ stdin.write stdin_str
12
+ stdin.close
13
+ end
14
+ stdout_str = stdout.read
15
+ stderr_str = stderr.read
16
+ exit_status = wait_thr.value
17
+ end
18
+ # puts [ command, stderr_str, exit_status ].inspect
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
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,43 @@
1
+ require 'net/ssh'
2
+
3
+ module Cindy
4
+ module Executor
5
+ class SSH
6
+ def initialize(cnx)
7
+ @cnx = cnx
8
+ end
9
+
10
+ def exec(command, stdin_str = nil, status_only = false)
11
+ exit_status = 1
12
+ # if stdin_str
13
+ stdout_str = stderr_str = ''
14
+ @cnx.open_channel do |channel|
15
+ channel.exec(command) do |ch, success|
16
+ channel.on_data do |ch, data|
17
+ stdout_str += data
18
+ end
19
+ channel.on_extended_data do |ch, type, data|
20
+ stderr_str += data
21
+ end
22
+ channel.on_request 'exit-status' do |ch, data|
23
+ exit_status = data.read_long
24
+ end
25
+ channel.send_data stdin_str if stdin_str
26
+ channel.eof!
27
+ end
28
+ end
29
+ @cnx.loop
30
+ # else
31
+ # result = @cnx.exec!(command)
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
36
+ end
37
+
38
+ def close
39
+ @cnx.close
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,130 @@
1
+ require 'erb'
2
+ require 'uri'
3
+ require 'ostruct'
4
+
5
+ module Cindy
6
+ class Template
7
+
8
+ attr_reader :file, :alias, :paths, :defvars, :envvars
9
+
10
+ def initialize(file, name)
11
+ @file = file # local template filename
12
+ @alias = name
13
+ @paths = {} # remote filenames (<environment name> => <filename>)
14
+ @defvars = {} # default/global variables
15
+ @envvars = {} # environment specific variables
16
+ end
17
+
18
+ IDENT_STRING = ' ' * 4
19
+
20
+ def to_s
21
+ ret = ["template :#{@alias}, #{@file.inspect} do"]
22
+ @defvars.each_pair do |k,v|
23
+ ret << "#{IDENT_STRING * 1}var :#{k}, #{v.inspect}"
24
+ end
25
+ @paths.each_pair do |ke,ve|
26
+ ret << "#{IDENT_STRING * 1}on :#{ke}, #{ve.inspect} do"
27
+ @envvars[ke].each_pair do |kv,vv|
28
+ ret << "#{IDENT_STRING * 2}var :#{kv}, #{vv.inspect}"
29
+ end
30
+ ret << "#{IDENT_STRING * 1}end"
31
+ end
32
+ ret << "end"
33
+ ret << ''
34
+ ret.join "\n"
35
+ end
36
+
37
+ def print(env)
38
+ puts render(env)
39
+ end
40
+
41
+ def deploy(env)
42
+ executor = executor_for_env env
43
+ remote_filename = @paths[env.name]
44
+ sudo = ''
45
+ sudo = 'sudo ' unless 0 == executor.exec('id -u').to_i
46
+ suffix = executor.exec('date \'+%Y%m%d%H%M\'') # use remote - not local - time machine
47
+ 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))
49
+ executor.exec("#{sudo} ln -snf \"#{remote_filename}.#{suffix}\" \"#{remote_filename}\"")
50
+ executor.close
51
+ end
52
+
53
+ # def variables
54
+ # (@defvars.keys + @envvars.collect { |v| v[1].keys }.flatten).uniq
55
+ # end
56
+
57
+ def list_variables(envname)
58
+ @defvars.merge(@envvars[envname]).each_pair do |k,v|
59
+ puts "- #{k}#{' (default)' unless @envvars[envname].key? k } = #{v} (#{v.class.name})"
60
+ end
61
+ end
62
+
63
+ # def unset_variable(varname)
64
+ # @defvars.delete varname
65
+ # @envvars.each_value do |h|
66
+ # h.delete varname
67
+ # end
68
+ # end
69
+
70
+ # def rename_variable(oldvarname, newvarname)
71
+ # @defvars[newvarname] = value if value = @defvars.delete(oldvarname)
72
+ # @envvars.each_value do |h|
73
+ # h[newvarname] = value if value = h.delete(oldvarname)
74
+ # end
75
+ # end
76
+
77
+ def set_variable(envname, varname, value)
78
+ envname = envname.intern if envname
79
+ varname = varname.intern
80
+ STDERR.puts "[ WARN ] non standard variable name found" unless varname =~ /\A[a-z][a-z0-9_]*\z/
81
+ if envname
82
+ @envvars[envname][varname] = value
83
+ else
84
+ @defvars[varname] = value
85
+ end
86
+ end
87
+
88
+ def set_path_for_environment(envname, path)
89
+ envname = envname.intern
90
+ @paths[envname] = path
91
+ @envvars[envname] ||= {}
92
+ end
93
+
94
+ private
95
+
96
+ def executor_for_env(env)
97
+ uri = URI.parse(env.uri)
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
106
+ end
107
+
108
+ def render(env, executor = nil)
109
+ close_executor = executor.nil?
110
+ executor ||= executor_for_env(env)
111
+ # shell = executor.exec('ps -p $$ -ocomm=')
112
+ vars = Hash[
113
+ @defvars.merge(@envvars[env.name]).map do |k, v|
114
+ if v.respond_to? :call
115
+ [ k, v.call(executor) ]
116
+ else
117
+ [ k, v ]
118
+ end
119
+ end
120
+ ]
121
+ # ||= to not overwrite a previously user defined variable with the same name
122
+ vars['_install_file_'] ||= @paths[env.name]
123
+ vars['_install_dir_'] ||= File.dirname @paths[env.name]
124
+ erb = ERB.new(File.read(@file), 0, '-')
125
+ executor.close if close_executor
126
+ erb.result(OpenStruct.new(vars).instance_eval { binding })
127
+ end
128
+
129
+ end
130
+ end
@@ -0,0 +1,28 @@
1
+ module Cindy
2
+ class Variable
3
+ TAG_NAME = self.name.split('::').last.downcase
4
+
5
+ class << self
6
+ def parse_boolean(string)
7
+ 'true' == string
8
+ end
9
+
10
+ def parse_string(string)
11
+ string
12
+ end
13
+
14
+ def parse_command(string)
15
+ Command.new string
16
+ end
17
+
18
+ def parse_int(string)
19
+ string.to_i
20
+ end
21
+ end
22
+
23
+ TYPES = public_methods.inject([]) do |ret,v|
24
+ ret << $' if v =~ /\Aparse_/
25
+ ret
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,3 @@
1
+ module Cindy
2
+ VERSION = "0.1.0"
3
+ end
@@ -0,0 +1,4 @@
1
+ $LOAD_PATH.unshift File.expand_path('../../lib', __FILE__)
2
+ require 'cindy/all'
3
+
4
+ require 'minitest/autorun'
@@ -0,0 +1,9 @@
1
+ require 'minitest_helper'
2
+
3
+ class CindyTest < Minitest::Test
4
+
5
+ def test_true
6
+ assert true
7
+ end
8
+
9
+ end
@@ -0,0 +1,40 @@
1
+ require 'minitest_helper'
2
+
3
+ class DSLTest < Minitest::Test
4
+
5
+ def test_on_without_block
6
+ cindy = Cindy::Cindy.from_string <<-EOS
7
+ environment :foo
8
+
9
+ template :bar, 'abc' do
10
+ on :foo, 'def'
11
+ end
12
+ EOS
13
+
14
+ tpl = cindy.templates[0]
15
+ assert_equal tpl.alias, :bar
16
+ assert_equal tpl.paths[:foo], 'def'
17
+ end
18
+
19
+ def test_string_equivalent_to_symbol
20
+ cindy = Cindy::Cindy.from_string <<-EOS
21
+ environment 'foo'
22
+
23
+ template 'bar', 'abc' do
24
+ on 'foo', 'def' do
25
+ var 'x', 'y'
26
+ end
27
+ end
28
+ EOS
29
+
30
+ assert cindy.has_environment? :foo
31
+ assert cindy.has_environment? 'foo'
32
+ assert cindy.has_template? :bar
33
+ assert cindy.has_template? 'bar'
34
+ tpl = cindy.templates[0]
35
+ assert tpl.envvars.key? :foo
36
+ # assert tpl.has_variable? :x
37
+ # assert tpl.has_variable? 'x'
38
+ end
39
+
40
+ end
metadata ADDED
@@ -0,0 +1,119 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: cindy-cm
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - julp
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2015-03-10 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: net-ssh
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: highline
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: bundler
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rake
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ description:
70
+ email:
71
+ executables:
72
+ - cindy
73
+ extensions: []
74
+ extra_rdoc_files: []
75
+ files:
76
+ - Changelog.md
77
+ - Gemfile
78
+ - README.md
79
+ - Rakefile
80
+ - bin/cindy
81
+ - cindy.gemspec
82
+ - lib/cindy/all.rb
83
+ - lib/cindy/cindy.rb
84
+ - lib/cindy/cli.rb
85
+ - lib/cindy/command.rb
86
+ - lib/cindy/environment.rb
87
+ - lib/cindy/executor/local.rb
88
+ - lib/cindy/executor/ssh.rb
89
+ - lib/cindy/template.rb
90
+ - lib/cindy/variable.rb
91
+ - lib/cindy/version.rb
92
+ - test/minitest_helper.rb
93
+ - test/test_cindy.rb
94
+ - test/test_dsl.rb
95
+ homepage: https://github.com/julp/cindy
96
+ licenses:
97
+ - BSD
98
+ metadata: {}
99
+ post_install_message:
100
+ rdoc_options: []
101
+ require_paths:
102
+ - lib
103
+ required_ruby_version: !ruby/object:Gem::Requirement
104
+ requirements:
105
+ - - ">="
106
+ - !ruby/object:Gem::Version
107
+ version: 2.0.0
108
+ required_rubygems_version: !ruby/object:Gem::Requirement
109
+ requirements:
110
+ - - ">="
111
+ - !ruby/object:Gem::Version
112
+ version: '0'
113
+ requirements: []
114
+ rubyforge_project:
115
+ rubygems_version: 2.4.6
116
+ signing_key:
117
+ specification_version: 4
118
+ summary: Turn out your configuration files into ERB templates and deploy them
119
+ test_files: []