cindy-cm 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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: []