kondate 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: 9b4da7ca37a8a499eb627e22c30c879d0b001433
4
+ data.tar.gz: 530957c521d781d58baa0fd2a6fe17711d0ac2e6
5
+ SHA512:
6
+ metadata.gz: 10db83b8b9862625811e18ea88e31c483b4cf1ab2956fe25a37fed61da23cab6951c70ca3a4e3693285ae460cc82e6f46b63c9478f2b8659efa848f6fe436698
7
+ data.tar.gz: bb83dc554b26890de637ec05f870af4d9a79d9450cea4c723a615847424bee8c3937ec32ba294e3b946e6f0174e50648fa467c19f6ca0306811923e89eddbfd4
@@ -0,0 +1,18 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+ /bootstrap.rb
11
+ /recipes/
12
+ /secrets/
13
+ /spec/
14
+ /properties/
15
+ /.kondate.conf
16
+ /hosts.yml
17
+ .ruby-version
18
+ /kondate/
@@ -0,0 +1,4 @@
1
+ language: ruby
2
+ rvm:
3
+ - 2.2.2
4
+ before_install: gem install bundler -v 1.10.6
@@ -0,0 +1,3 @@
1
+ # 0.1.0 (2015/11/23)
2
+
3
+ first version
@@ -0,0 +1,13 @@
1
+ # Contributor Code of Conduct
2
+
3
+ As contributors and maintainers of this project, we pledge to respect all people who contribute through reporting issues, posting feature requests, updating documentation, submitting pull requests or patches, and other activities.
4
+
5
+ We are committed to making participation in this project a harassment-free experience for everyone, regardless of level of experience, gender, gender identity and expression, sexual orientation, disability, personal appearance, body size, race, ethnicity, age, or religion.
6
+
7
+ Examples of unacceptable behavior by participants include the use of sexual language or imagery, derogatory comments or personal attacks, trolling, public or private harassment, insults, or other unprofessional conduct.
8
+
9
+ Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct. Project maintainers who do not follow the Code of Conduct may be removed from the project team.
10
+
11
+ Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by opening an issue or contacting one or more of the project maintainers.
12
+
13
+ This Code of Conduct is adapted from the [Contributor Covenant](http://contributor-covenant.org), version 1.0.0, available at [http://contributor-covenant.org/version/1/0/0/](http://contributor-covenant.org/version/1/0/0/)
data/Gemfile ADDED
@@ -0,0 +1,5 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
4
+ gem 'pry'
5
+ gem 'pry-nav'
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2015 sonots
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
@@ -0,0 +1,290 @@
1
+ # Kondate
2
+
3
+ Kondate is yet another nodes management framework for Itamae/Serverspec.
4
+
5
+ Kondate provides nodes/roles/attributes/run_lists management feature for Itamae/Serverspec.
6
+
7
+ ## Installation
8
+
9
+ Add this line to your application's Gemfile:
10
+
11
+ ```ruby
12
+ gem 'kondate'
13
+ ```
14
+
15
+ And then execute:
16
+
17
+ $ bundle
18
+
19
+ Or install it yourself as:
20
+
21
+ $ gem install kondate
22
+
23
+ ## Usage
24
+
25
+ Generate a template directory tree:
26
+
27
+ ```
28
+ $ bundle exec kondate generate [target_dir .]
29
+ ```
30
+
31
+ Run itamae:
32
+
33
+ ```
34
+ $ bundle exec kondate itamae <host>
35
+ ```
36
+
37
+ Run serverspec:
38
+
39
+ ```
40
+ $ bundle exec kondate serverspec <host>
41
+ ```
42
+
43
+ ## Configuration
44
+
45
+ `kondate generate` provides a template directory tree such as:
46
+
47
+ ```
48
+ .
49
+ ├── .kondate.conf # kondate configuration
50
+ ├── bootstrap.rb # itamae bootstrap
51
+ ├── hosts.yml # manages hostnames and its roles
52
+ ├── properties # manages run_lists and attributes
53
+ │   ├── nodes # host specific properties
54
+ │   └── roles # role properties
55
+ │   └── sample.yml
56
+ ├── secrets # manages secrets attributes such as passwords
57
+ │   └── properties
58
+ │   ├── nodes
59
+ │   └── roles
60
+ ├── recipes # itamae recipes
61
+ │   ├── middleware # middleware recipes
62
+ │   │   └── base
63
+ │   │   └── default.rb
64
+ │   └── roles # role recipes
65
+ └── spec # serverspec specs
66
+ ├── middleware # middleware recipes specs
67
+ │   └── base_spec.rb
68
+ ├─ roles # role recipes specs
69
+ └── spec_helper.rb
70
+ ```
71
+
72
+ ### .kondate.conf
73
+
74
+ The default .kondate.conf looks like below:
75
+
76
+ ```
77
+ middlware_recipes_dir: recipes/middleware
78
+ roles_recipes_dir: recipes/roles
79
+ middleware_recipes_serverspec_dir: spec/middleware
80
+ roles_recipes_serverspec_dir: spec/roles
81
+ nodes_properties_dir: properties/nodes
82
+ roles_properties_dir: properties/roles
83
+ secret_nodes_properties_dir: secrets/properties/nodes
84
+ secret_roles_properties_dir: secrets/properties/roles
85
+ plugin_dir: lib
86
+ host_plugin:
87
+ type: file
88
+ path: hosts.yml
89
+ ```
90
+
91
+ You can customize the directory tree with this conf.
92
+
93
+ ### hosts.yml
94
+
95
+ The default uses `file` host plugin, and `hosts.yml`. The contents of `hosts.yml` look like below:
96
+
97
+ ```
98
+ localhost: [sample]
99
+ ```
100
+
101
+ where keys are host names, and values are array of roles.
102
+
103
+ ```
104
+ $ bundle exec kondate itamae <host>
105
+ ```
106
+
107
+ works as follows:
108
+
109
+ 1. obtains a role list from `hosts.yml`
110
+ 2. reads `properties/roles/#{role}.yml`, and find recipes and its attributes
111
+ 3. runs recipes
112
+
113
+ You can create your own host plugin. See `Host Plugin` section for more details.
114
+
115
+ ### properties
116
+
117
+ Property files are places to write recipes to run and attributes values.
118
+
119
+ ```
120
+ ├── properties # manages run_lists and attributes
121
+ │   ├── nodes # host specific properties
122
+ │   └── roles # role properties
123
+ │   └── sample.yml
124
+ ```
125
+
126
+ An example looks like below:
127
+
128
+ properties/roles/#{role}.yml
129
+
130
+ ```
131
+ attributes:
132
+ rbenv:
133
+ versions: [2.2.3]
134
+ gems:
135
+ 2.2.3: [bundler]
136
+ ndenv:
137
+ versions: [v0.12.2]
138
+ global: v0.12.2
139
+ nginx:
140
+ ```
141
+
142
+ The attributes variables are accessible like `attrs['rbenv']['versions']` in recipes, which is equivalent and short version of `node['attributes']['rbenv']['versions']`.
143
+
144
+ You can also prepare a host-specific property file such as:
145
+
146
+ properties/nodes/#{host}.yml
147
+
148
+ ```
149
+ attributes:
150
+ nginx:
151
+ worker_processes: 8
152
+ ```
153
+
154
+ These files are merged on kondate execution in order of `role` + `node` (`node` file overwrites `role` file).
155
+
156
+ ### secret properties
157
+
158
+ Secret properties are places to write confidential attributes.
159
+
160
+ ```
161
+ ├── secrets # manages secrets attributes such as passwords
162
+ │   └── properties
163
+ │   ├── nodes
164
+ │   └── roles
165
+ ```
166
+
167
+ An example looks like below:
168
+
169
+ secrets/properties/roles/sample.yml
170
+
171
+ ```
172
+ attributes:
173
+ base:
174
+ password: xxxxxxxx
175
+ ```
176
+
177
+ These files are merged with property files on kondate execution.
178
+
179
+ Hint: I manage secret property files on github private repository. ToDo: support encryption.
180
+
181
+ ### recipes
182
+
183
+ Put you itamae recipes:
184
+
185
+ ```
186
+ ├── recipes # itamae recipes
187
+ │   ├── middleware # middleware recipes
188
+ │   │   └── base
189
+ │   │   └── default.rb
190
+ │   └── roles # role recipes
191
+ ```
192
+
193
+ `middleware recipes` are usual recipes to write how to install middleware such as `nginx`, `mysql`.
194
+
195
+ `role recipes` are places to write role-specific provisioning. I often write recipes to create log directories for my app (role), for example.
196
+
197
+ recipes/roles/myapp/default.rb
198
+
199
+ ```ruby
200
+ directory "/var/log/myapp" do
201
+ owner myapp
202
+ group myapp
203
+ mode 0755
204
+ end
205
+ ```
206
+
207
+ #### spec
208
+
209
+ Put your serverspec specs.
210
+
211
+ ```
212
+ └── spec # serverspec specs
213
+ ├── middleware # middleware recipes specs
214
+ └── roles # role recipes specs
215
+ ```
216
+
217
+ It is required that `spec/spec_helper` has lines:
218
+
219
+ ```ruby
220
+ set :host, ENV['TARGET_HOST']
221
+ set :set_property, YAML.load_file(ENV['TARGET_NODE_FILE'])
222
+ ```
223
+
224
+ because these ENVs are passed by `kondate serverspec`.
225
+
226
+ ## Host Plugin
227
+
228
+ The default reads `hosts.yml` to resolve roles of a host, but
229
+ you may want to resolve roles from AWS EC2 `roles` tag, or
230
+ you may want to resolve roles from your own host resolver API application.
231
+
232
+ Thus, `kondate` provides a plugin system to reolve hosts' roles.
233
+
234
+ ### Naming Convention
235
+
236
+ You must follow the below naming conventions:
237
+
238
+ * gem name: kondate-host_plugin-xxx (xxx_yyy)
239
+ * file name: lib/kondate/host_plugin/xxx.rb (xxx_yyy.rb)
240
+ * class name: Kondate::HostPlugin::Xxx (XxxYyy)
241
+
242
+ ### Interface
243
+
244
+ What you have to implement is `#initialize` and `#get_roles` methods. Here is an example of file plugin:
245
+
246
+ ```ruby
247
+ require 'yaml'
248
+
249
+ module Kondate
250
+ module HostPlugin
251
+ class File
252
+ # @param [HashWithIndifferentAccess] config
253
+ def initialize(config)
254
+ raise ConfigError.new('file: path is not configured') unless config.path
255
+ @path = config.path
256
+ end
257
+
258
+ # @param [String] host hostname
259
+ # @return [Array] array of roles
260
+ def get_roles(host)
261
+ # YAML format
262
+ #
263
+ # host1: [role1, role2]
264
+ # host2: [role1, role2]
265
+ YAML.load_file(@path)[host]
266
+ end
267
+ end
268
+ end
269
+ end
270
+ ```
271
+
272
+ ### Config
273
+
274
+ `config` parameter of `#initialize` is created from the configuration file (.kondate.conf):
275
+
276
+ ```
277
+ host_plugin:
278
+ type: file
279
+ path: hosts.yml
280
+ ```
281
+
282
+ `config.type` and `config.path` is available in the above config.
283
+
284
+ ## ToDo
285
+
286
+ write tests
287
+
288
+ ## License
289
+
290
+ The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
@@ -0,0 +1,10 @@
1
+ require "bundler/gem_tasks"
2
+ require "rake/testtask"
3
+
4
+ Rake::TestTask.new(:test) do |t|
5
+ t.libs << "test"
6
+ t.libs << "lib"
7
+ t.test_files = FileList['test/**/*_test.rb']
8
+ end
9
+
10
+ task :default => :test
@@ -0,0 +1,20 @@
1
+ # -*- mode: ruby -*-
2
+ # vi: set ft=ruby :
3
+
4
+ # Vagrantfile API/syntax version. Don't touch unless you know what you're doing!
5
+ VAGRANTFILE_API_VERSION = "2"
6
+
7
+ Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
8
+ # All Vagrant configuration is done here. The most common configuration
9
+ # options are documented and commented below. For a complete reference,
10
+ # please see the online documentation at vagrantup.com.
11
+ # Every Vagrant virtual environment requires a box to build off of.
12
+ config.vm.box = "centos6.5.3"
13
+ config.vm.box_url = 'https://github.com/2creatives/vagrant-centos/releases/download/v6.5.3/centos65-x86_64-20140116.box' # name
14
+ config.vm.define "vagrant-centos"
15
+ config.ssh.forward_agent = true
16
+ config.vm.provider "virtualbox" do |vb|
17
+ # Use VBoxManage to customize the VM. For example to change memory:
18
+ vb.customize ["modifyvm", :id, "--memory", "1024"]
19
+ end
20
+ end
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "kondate"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require "irb"
14
+ IRB.start
@@ -0,0 +1,7 @@
1
+ #!/bin/bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+
5
+ bundle install
6
+
7
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ CMD = File.join(File.dirname(__FILE__), 'kondate')
4
+ if ARGV.size == 0 || ARGV[0] == "-h" || ARGV[0] == "--help"
5
+ exec CMD, 'help', 'itamae'
6
+ else
7
+ exec CMD, 'itamae', *ARGV
8
+ end
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "kondate/cli"
4
+ Kondate::CLI.start(ARGV)
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ CMD = File.join(File.dirname(__FILE__), 'kondate')
4
+ if ARGV.size == 0 || ARGV[0] == "-h" || ARGV[0] == "--help"
5
+ exec CMD, 'help', 'serverspec'
6
+ else
7
+ exec CMD, 'serverspec', *ARGV
8
+ end
@@ -0,0 +1,30 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'kondate/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "kondate"
8
+ spec.version = Kondate::VERSION
9
+ spec.authors = ["sonots"]
10
+ spec.email = ["sonots@gmail.com"]
11
+
12
+ spec.summary = %q{Kondate is yet another nodes management framework for Itamae/Serverspec.}
13
+ spec.description = %q{Kondate is yet another nodes management framework for Itamae/Serverspec.}
14
+ spec.homepage = "https://github.com/sonots/kondate"
15
+ spec.license = "MIT"
16
+
17
+ spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
18
+ spec.bindir = "exe"
19
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
20
+ spec.require_paths = ["lib"]
21
+
22
+ spec.add_dependency 'itamae'
23
+ spec.add_dependency 'serverspec'
24
+ spec.add_dependency 'thor'
25
+ spec.add_dependency 'highline'
26
+
27
+ spec.add_development_dependency "bundler"
28
+ spec.add_development_dependency "rake"
29
+ spec.add_development_dependency "test-unit"
30
+ end
@@ -0,0 +1,39 @@
1
+ # activesupport/core_ext/hash/deep_merge.rb
2
+ class Hash
3
+ # Returns a new hash with +self+ and +other_hash+ merged recursively.
4
+ #
5
+ # h1 = { a: true, b: { c: [1, 2, 3] } }
6
+ # h2 = { a: false, b: { x: [3, 4, 5] } }
7
+ #
8
+ # h1.deep_merge(h2) #=> { a: false, b: { c: [1, 2, 3], x: [3, 4, 5] } }
9
+ #
10
+ # Like with Hash#merge in the standard library, a block can be provided
11
+ # to merge values:
12
+ #
13
+ # h1 = { a: 100, b: 200, c: { c1: 100 } }
14
+ # h2 = { b: 250, c: { c1: 200 } }
15
+ # h1.deep_merge(h2) { |key, this_val, other_val| this_val + other_val }
16
+ # # => { a: 100, b: 450, c: { c1: 300 } }
17
+ def deep_merge(other_hash, &block)
18
+ dup.deep_merge!(other_hash, &block)
19
+ end
20
+
21
+ # Same as +deep_merge+, but modifies +self+.
22
+ def deep_merge!(other_hash, &block)
23
+ other_hash.each_pair do |current_key, other_value|
24
+ this_value = self[current_key]
25
+
26
+ self[current_key] = if this_value.is_a?(Hash) && other_value.is_a?(Hash)
27
+ this_value.deep_merge(other_value, &block)
28
+ else
29
+ if block_given? && key?(current_key)
30
+ block.call(current_key, this_value, other_value)
31
+ else
32
+ other_value
33
+ end
34
+ end
35
+ end
36
+
37
+ self
38
+ end
39
+ end
@@ -0,0 +1,36 @@
1
+ # top level at recipe
2
+ module Itamae
3
+ class Recipe
4
+ class EvalContext
5
+ def attrs
6
+ node[:attributes]
7
+ end
8
+ end
9
+ end
10
+ end
11
+
12
+ # resource { here }
13
+ module Itamae
14
+ module Resource
15
+ class Base
16
+ class EvalContext
17
+ def attrs
18
+ node[:attributes]
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
24
+
25
+ # templates
26
+ module Itamae
27
+ module Resource
28
+ class Template < RemoteFile
29
+ class RenderContext
30
+ def attrs
31
+ node[:attributes]
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,3 @@
1
+ require 'itamae'
2
+ require_relative 'attributes'
3
+
@@ -0,0 +1,9 @@
1
+ module Specinfra
2
+ module Helper
3
+ module Properties
4
+ def attrs
5
+ property['attributes']
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,3 @@
1
+ # require 'serverspec' # stack level too deep
2
+ require_relative 'attributes'
3
+
@@ -0,0 +1,8 @@
1
+ HOKKE_GEM_ROOT = File.expand_path('..', File.dirname(__FILE__)) unless defined?(HOKKE_GEM_ROOT)
2
+
3
+ require "kondate/version"
4
+ require "kondate/config"
5
+ require "kondate/property_builder"
6
+ require "ext/hash/deep_merge"
7
+ require "kondate/string_util"
8
+ require "kondate/error"
@@ -0,0 +1,144 @@
1
+ require 'thor'
2
+ require 'yaml'
3
+ require 'net/ssh'
4
+ require 'rspec/core/rake_task'
5
+ require "highline/import"
6
+ require_relative '../kondate'
7
+ require 'fileutils'
8
+ require 'shellwords'
9
+ require 'find'
10
+
11
+ module Kondate
12
+ class CLI < Thor
13
+ # cf. http://qiita.com/KitaitiMakoto/items/c6b9d6311c20a3cc21f9
14
+ def self.exit_on_failure?
15
+ true
16
+ end
17
+
18
+ class_option :config, :aliases => ["-c"], :type => :string, :default => nil
19
+ class_option :dry_run, :type => :boolean, :default => false
20
+ # default_command :itamae
21
+
22
+ def initialize(args = [], opts = [], config = {})
23
+ super
24
+ Config.configure(@options)
25
+ end
26
+
27
+ desc "generate [target_dir = .]", "Generate kondate directory tree template"
28
+ def generate(target_dir = '.')
29
+ Config.kondate_directories.each do |_, dir|
30
+ $stdout.puts "mkdir -p #{File.join(target_dir, dir)}"
31
+ FileUtils.mkdir_p(File.join(target_dir, dir)) unless @options[:dry_run]
32
+ end
33
+
34
+ templates_dir = File.join(HOKKE_GEM_ROOT, 'lib', 'kondate', 'templates')
35
+ templates_dir_length = templates_dir.length
36
+ Find.find(templates_dir).select {|f| File.file?(f) }.each do |src|
37
+ dst = File.join(target_dir, src[templates_dir_length+1 .. -1])
38
+ dst_dir = File.dirname(dst)
39
+ unless Dir.exist?(dst_dir)
40
+ $stdout.puts "mkdir -p #{dst_dir}"
41
+ FileUtils.mkdir_p(dst_dir) unless @options[:dry_run]
42
+ end
43
+ $stdout.puts "cp #{src} #{dst}"
44
+ FileUtils.copy(src, dst) unless @options[:dry_run]
45
+ end
46
+ end
47
+
48
+ desc "itamae <host>", "Execute itamae"
49
+ option :role, :type => :array, :default => []
50
+ option :recipe, :type => :array, :default => []
51
+ option :debug, :aliases => ["-d"], :type => :boolean, :default => false
52
+ option :confirm, :type => :boolean, :default => true
53
+ def itamae(host)
54
+ builder, property_files = build_property_files(host)
55
+
56
+ property_files.each do |role, property_file|
57
+ ENV['TARGET_HOST'] = host
58
+ ENV['RUBYOPT'] = "-I #{Config.plugin_dir} -r bundler/setup -r ext/itamae/kondate"
59
+ command = "bundle exec itamae ssh"
60
+ command << " -h #{host}"
61
+
62
+ properties = YAML.load_file(property_file)
63
+
64
+ if builder.vagrant?
65
+ command << " --vagrant"
66
+ else
67
+ config = Net::SSH::Config.for(host)
68
+ command << " -u #{properties['ssh_user'] || config[:user] || ENV['USER']}"
69
+ command << " -i #{(properties['ssh_keys'] || []).first || (config[:ssh_keys] || []).first || (File.exist?(File.expand_path('~/.ssh/id_dsa')) ? '~/.ssh/id_dsa' : '~/.ssh/id_rsa')}"
70
+ command << " -p #{properties['ssh_port'] || config[:port] || 22}"
71
+ end
72
+
73
+ command << " -y #{property_file}"
74
+ command << " -l=debug" if @options[:debug]
75
+ command << " --dry-run" if @options[:dry_run]
76
+ command << " bootstrap.rb"
77
+ $stdout.puts command
78
+ exit(-1) unless system(command)
79
+ end
80
+ end
81
+
82
+ desc "serverspec <host>", "Execute serverspec"
83
+ option :role, :type => :array, :default => []
84
+ option :recipe, :type => :array, :default => []
85
+ option :debug, :aliases => ["-d"], :type => :boolean, :default => false
86
+ option :confirm, :type => :boolean, :default => true
87
+ def serverspec(host)
88
+ builder, property_files = build_property_files(host)
89
+
90
+ ENV['RUBYOPT'] = "-I #{Config.plugin_dir} -r bundler/setup -r ext/serverspec/kondate"
91
+ property_files.each do |role, property_file|
92
+ RSpec::Core::RakeTask.new([host, role].join(':'), :recipe) do |t, args|
93
+ ENV['TARGET_HOST'] = host
94
+
95
+ ENV['TARGET_NODE_FILE'] = property_file
96
+ recipes = YAML.load_file(property_file)['attributes'].keys.map {|recipe|
97
+ File.join(Config.middleware_recipes_serverspec_dir, recipe)
98
+ }
99
+ recipes << File.join(Config.roles_recipes_serverspec_dir, role)
100
+ t.pattern = '{' + recipes.join(',') + '}_spec.rb'
101
+ end
102
+
103
+ Rake::Task["#{host}:#{role}"].invoke(@options[:recipe])
104
+ end
105
+ end
106
+
107
+ private
108
+
109
+ def build_property_files(host)
110
+ builder = PropertyBuilder.new(host)
111
+ roles = @options[:role] ? builder.filter_roles(@options[:role]) : builder.roles
112
+ $stderr.puts 'No role' and exit(1) if roles.empty?
113
+ $stdout.puts "roles: [#{roles.join(', ')}]"
114
+
115
+ property_files = {}
116
+ roles.each do |role|
117
+ if path = builder.install(role, @options[:recipe])
118
+ property_files[role] = path
119
+ $stdout.puts "# #{role}"
120
+ $stdout.puts mask_secrets(File.read(path))
121
+ else
122
+ $stdout.puts "# #{role} (no attribute, skipped)"
123
+ end
124
+ end
125
+
126
+ if property_files.empty?
127
+ $stderr.puts "Nothing to run"
128
+ exit(1)
129
+ end
130
+
131
+ if @options[:confirm]
132
+ prompt = ask "Proceed? (y/n):"
133
+ exit(0) unless prompt == 'y'
134
+ end
135
+
136
+ [builder, property_files]
137
+ end
138
+
139
+ def mask_secrets(str)
140
+ str.gsub(/(.*key[^:]*): (.*)$/, '\1: *******').
141
+ gsub(/(.*password[^:]*): (.*)$/, '\1: *******')
142
+ end
143
+ end
144
+ end
@@ -0,0 +1,101 @@
1
+ require 'thor/core_ext/hash_with_indifferent_access'
2
+
3
+ module Kondate
4
+ class Config
5
+ class << self
6
+ def configure(opts = {})
7
+ @opts = opts
8
+ reset
9
+ self
10
+ end
11
+
12
+ def opts
13
+ @opts ||= {}
14
+ end
15
+
16
+ def reset
17
+ @config_path = nil
18
+ @config = nil
19
+ end
20
+
21
+ DEFAULT_CONFIG_PATH = '.kondate.conf'
22
+
23
+ def config
24
+ return @config if @config
25
+ if config_path == DEFAULT_CONFIG_PATH && !File.exist?(config_path)
26
+ @config = Thor::CoreExt::HashWithIndifferentAccess.new({})
27
+ else
28
+ @config = Thor::CoreExt::HashWithIndifferentAccess.new(YAML.load_file(config_path))
29
+ end
30
+ end
31
+
32
+ def config_path
33
+ @config_path ||= opts[:config] || ENV['HOKKE_CONFIG_PATH'] || DEFAULT_CONFIG_PATH
34
+ end
35
+
36
+ def kondate_directories
37
+ {
38
+ 'middleware_recipes_dir' => middleware_recipes_dir,
39
+ 'roles_recipes_dir' => roles_recipes_dir,
40
+ 'middleware_recipes_serverspec_dir' => middleware_recipes_serverspec_dir,
41
+ 'roles_recipes_serverspec_dir' => roles_recipes_serverspec_dir,
42
+ 'nodes_properties_dir' => nodes_properties_dir,
43
+ 'roles_properties_dir' => roles_properties_dir,
44
+ 'secret_nodes_properties_dir' => secret_nodes_properties_dir,
45
+ 'secret_roles_properties_dir' => secret_roles_properties_dir,
46
+ }
47
+ end
48
+
49
+ def middleware_recipes_dir
50
+ config[:middleware_recipes_dir] || 'recipes/middleware'
51
+ end
52
+
53
+ def roles_recipes_dir
54
+ config[:roles_recipes_dir] || 'recipes/roles'
55
+ end
56
+
57
+ def middleware_recipes_serverspec_dir
58
+ config[:middleware_recipes_serverspec_dir] || 'spec/middleware'
59
+ end
60
+
61
+ def roles_recipes_serverspec_dir
62
+ config[:roles_recipes_serverspec_dir] || 'spec/roles'
63
+ end
64
+
65
+ def nodes_properties_dir
66
+ config[:nodes_properties_dir] || 'properties/nodes'
67
+ end
68
+
69
+ def roles_properties_dir
70
+ config[:roles_properties_dir] || 'properties/roles'
71
+ end
72
+
73
+ def secret_nodes_properties_dir
74
+ config[:secret_nodes_properties_dir] || 'secrets/properties/nodes'
75
+ end
76
+
77
+ def secret_roles_properties_dir
78
+ config[:secret_roles_properties_dir] || 'secrets/properties/roles'
79
+ end
80
+
81
+ def plugin_dir
82
+ File.expand_path(config[:plugin_dir] || 'lib')
83
+ end
84
+
85
+ def host_plugin
86
+ return @host_plugin if @host_plugin
87
+ plugin = Thor::CoreExt::HashWithIndifferentAccess.new(config[:host_plugin] || {
88
+ 'type' => 'file',
89
+ 'path' => 'hosts.yml'
90
+ })
91
+ begin
92
+ require File.join(Config.plugin_dir, "kondate/host_plugin/#{plugin.type}")
93
+ rescue LoadError => e
94
+ require "kondate/host_plugin/#{plugin.type}"
95
+ end
96
+ class_name = "Kondate::HostPlugin::#{StringUtil.camelize(plugin.type)}"
97
+ @host_plugin = Object.const_get(class_name).new(plugin)
98
+ end
99
+ end
100
+ end
101
+ end
@@ -0,0 +1,6 @@
1
+ module Kondate
2
+ class Error < StandardError
3
+ end
4
+ class ConfigError < StandardError
5
+ end
6
+ end
@@ -0,0 +1,20 @@
1
+ require 'yaml'
2
+
3
+ module Kondate
4
+ module HostPlugin
5
+ class File
6
+ def initialize(config)
7
+ raise ConfigError.new('file: path is not configured') unless config.path
8
+ @path = config.path
9
+ end
10
+
11
+ def get_roles(host)
12
+ # YAML format
13
+ #
14
+ # host1: [role1, role2]
15
+ # host2: [role1, role2]
16
+ YAML.load_file(@path)[host]
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,106 @@
1
+ require 'yaml'
2
+ require 'tempfile'
3
+
4
+ module Kondate
5
+ class PropertyBuilder
6
+ attr_reader :host
7
+
8
+ def initialize(host)
9
+ @host = host
10
+ end
11
+
12
+ def roles
13
+ @roles ||=
14
+ begin
15
+ vagrant? ? [] : Config.host_plugin.get_roles(@host)
16
+ rescue => e
17
+ $stderr.puts "cannot get roles from host:#{@host}, #{e.class} #{e.message}"
18
+ []
19
+ end
20
+ end
21
+
22
+ def filter_roles(filters)
23
+ filters = Array(filters).map {|role| role.gsub(':', '-') }
24
+ if roles.empty? # maybe, development (vagrant) env
25
+ @roles = filters # append specified roles
26
+ @roles.each do |role|
27
+ unless File.exist?(role_file(role))
28
+ $stderr.puts "#{role_file(role)} does not exist, possibly typo?"
29
+ exit(1)
30
+ end
31
+ end
32
+ else
33
+ if (filters - roles).size > 0
34
+ $stderr.puts "cannot specify #{(filters - roles).first}"
35
+ exit(1)
36
+ end
37
+ unless filters.empty?
38
+ # filter out for production env
39
+ @roles = roles & filters
40
+ end
41
+ end
42
+ @roles
43
+ end
44
+
45
+ def node_file
46
+ File.join(Config.nodes_properties_dir, "#{@host}.yml")
47
+ end
48
+
49
+ def secret_node_file
50
+ File.join(Config.secret_nodes_properties_dir, "#{@host}.yml")
51
+ end
52
+
53
+ def role_file(role)
54
+ File.join(Config.roles_properties_dir, "#{role}.yml")
55
+ end
56
+
57
+ def secret_role_file(role)
58
+ File.join(Config.secret_roles_properties_dir, "#{role}.yml")
59
+ end
60
+
61
+ def get_content(yaml_file)
62
+ content = File.exist?(yaml_file) ? YAML.load_file(yaml_file) : {}
63
+ content.is_a?(Hash) ? content : {}
64
+ end
65
+
66
+ # Generate tmp node file (for each role)
67
+ #
68
+ # role_file + secret_role_file + nod_file + node_secret_file + roles: @roles
69
+ #
70
+ # This file is automatically created and removed
71
+ def install(role, filter_recipes = nil)
72
+ node_property = get_content(node_file)
73
+ secret_node_property = get_content(secret_node_file)
74
+ role_property = get_content(role_file(role))
75
+ secret_role_property = get_content(secret_role_file(role))
76
+
77
+ property = {
78
+ 'role' => role,
79
+ 'roles' => roles,
80
+ 'attributes' => {},
81
+ }.deep_merge!(role_property).
82
+ deep_merge!(secret_role_property).
83
+ deep_merge!(node_property).
84
+ deep_merge!(secret_node_property)
85
+
86
+ # filter out the recipe
87
+ if filter_recipes and !filter_recipes.empty?
88
+ property['attributes'].keys.each do |key|
89
+ property['attributes'].delete(key) unless filter_recipes.include?(key)
90
+ end
91
+ end
92
+
93
+ if property['attributes'].empty?
94
+ nil
95
+ else
96
+ Tempfile.open("provisioning_") do |fp|
97
+ YAML.dump(property, fp)
98
+ end.path
99
+ end
100
+ end
101
+
102
+ def vagrant?
103
+ %r{\Avagrant} === @host
104
+ end
105
+ end
106
+ end
@@ -0,0 +1,10 @@
1
+ module Kondate
2
+ class StringUtil
3
+ def self.camelize(string)
4
+ string = string.sub(/^[a-z\d]*/) { $&.capitalize }
5
+ string.gsub!(/(?:_|(\/))([a-z\d]*)/i) { $2.capitalize }
6
+ string.gsub!(/\//, '::')
7
+ string
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,13 @@
1
+ ---
2
+ middlware_recipes_dir: recipes/middleware
3
+ roles_recipes_dir: recipes/roles
4
+ middleware_recipes_serverspec_dir: spec/middleware
5
+ roles_recipes_serverspec_dir: spec/roles
6
+ nodes_properties_dir: properties/nodes
7
+ roles_properties_dir: properties/roles
8
+ secret_nodes_properties_dir: secrets/properties/nodes
9
+ secret_roles_properties_dir: secrets/properties/roles
10
+ plugin_dir: lib
11
+ host_plugin:
12
+ type: file
13
+ path: hosts.yml
@@ -0,0 +1,10 @@
1
+ require 'yaml'
2
+ require 'kondate'
3
+
4
+ recipes = node['attributes'].keys
5
+ recipes.each do |recipe|
6
+ include_recipe(File.join(Kondate::Config.middleware_recipes_dir, recipe, "default.rb"))
7
+ end
8
+ if File.exist?(File.join(Kondate::Config.roles_recipes_dir, node[:role], "default.rb"))
9
+ include_recipe(File.join(Kondate::Config.roles_recipes_dir, node[:role], "default.rb"))
10
+ end
@@ -0,0 +1 @@
1
+ localhost: [sample]
@@ -0,0 +1,3 @@
1
+ attributes:
2
+ base:
3
+ message: 'hello, kondate!'
@@ -0,0 +1,3 @@
1
+ execute "echo #{attrs['base']['message']}" do
2
+ command "echo #{attrs['base']['message']}"
3
+ end
@@ -0,0 +1,5 @@
1
+ require 'spec_helper'
2
+
3
+ describe file('/tmp') do
4
+ it { should be_directory }
5
+ end
@@ -0,0 +1,63 @@
1
+ require 'serverspec'
2
+ require 'net/ssh'
3
+ require 'tempfile'
4
+ require 'yaml'
5
+
6
+ ### required for kondate #####
7
+ host = ENV['TARGET_HOST']
8
+ set :set_property, YAML.load_file(ENV['TARGET_NODE_FILE'])
9
+ ############################
10
+
11
+ set :backend, :ssh
12
+
13
+ if ENV['ASK_SUDO_PASSWORD']
14
+ begin
15
+ require 'highline/import'
16
+ rescue LoadError
17
+ fail "highline is not available. Try installing it."
18
+ end
19
+ set :sudo_password, ask("Enter sudo password: ") { |q| q.echo = false }
20
+ else
21
+ set :sudo_password, ENV['SUDO_PASSWORD']
22
+ end
23
+
24
+ options =
25
+ if %r{\Avagrant} === host
26
+ `vagrant up #{host}`
27
+
28
+ config = Tempfile.new('', Dir.tmpdir)
29
+ config.write(`vagrant ssh-config #{host}`)
30
+ config.close
31
+
32
+ Net::SSH::Config.for(host, [config.path])
33
+ else
34
+ o = Net::SSH::Config.for(host)
35
+ ssh_config_options =
36
+ %w(encryption compression compression_level
37
+ timeout forward_agent global_known_hosts_file
38
+ auth_methods host_key host_key_alias host_name
39
+ keys keys_only hmac auth_methods port proxy
40
+ rekey_limit user user_known_hosts_file)
41
+
42
+ ssh_config_options.map do |option|
43
+ if property[option]
44
+ o[option.to_sym] = property[option]
45
+ end
46
+ end
47
+ o
48
+ end
49
+
50
+ options[:user] ||= Etc.getlogin
51
+
52
+ set :host, options[:host_name] || host
53
+ set :ssh_options, options
54
+
55
+ # Disable sudo
56
+ # set :disable_sudo, true
57
+
58
+
59
+ # Set environment variables
60
+ # set :env, :LANG => 'C', :LC_MESSAGES => 'C'
61
+
62
+ # Set PATH
63
+ # set :path, '/sbin:/usr/local/sbin:$PATH'
@@ -0,0 +1,3 @@
1
+ module Kondate
2
+ VERSION = "0.1.0"
3
+ end
metadata ADDED
@@ -0,0 +1,180 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: kondate
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - sonots
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2015-11-23 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: itamae
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: serverspec
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: thor
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :runtime
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: highline
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: bundler
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: rake
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: test-unit
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
111
+ description: Kondate is yet another nodes management framework for Itamae/Serverspec.
112
+ email:
113
+ - sonots@gmail.com
114
+ executables:
115
+ - itamae-kondate
116
+ - kondate
117
+ - serverspec-kondate
118
+ extensions: []
119
+ extra_rdoc_files: []
120
+ files:
121
+ - ".gitignore"
122
+ - ".travis.yml"
123
+ - CHANGELOG.md
124
+ - CODE_OF_CONDUCT.md
125
+ - Gemfile
126
+ - LICENSE.txt
127
+ - README.md
128
+ - Rakefile
129
+ - Vagrantfile
130
+ - bin/console
131
+ - bin/setup
132
+ - exe/itamae-kondate
133
+ - exe/kondate
134
+ - exe/serverspec-kondate
135
+ - kondate.gemspec
136
+ - lib/ext/hash/deep_merge.rb
137
+ - lib/ext/itamae/attributes.rb
138
+ - lib/ext/itamae/kondate.rb
139
+ - lib/ext/serverspec/attributes.rb
140
+ - lib/ext/serverspec/kondate.rb
141
+ - lib/kondate.rb
142
+ - lib/kondate/cli.rb
143
+ - lib/kondate/config.rb
144
+ - lib/kondate/error.rb
145
+ - lib/kondate/host_plugin/file.rb
146
+ - lib/kondate/property_builder.rb
147
+ - lib/kondate/string_util.rb
148
+ - lib/kondate/templates/.kondate.conf
149
+ - lib/kondate/templates/bootstrap.rb
150
+ - lib/kondate/templates/hosts.yml
151
+ - lib/kondate/templates/properties/roles/sample.yml
152
+ - lib/kondate/templates/recipes/middleware/base/default.rb
153
+ - lib/kondate/templates/spec/middleware/base_spec.rb
154
+ - lib/kondate/templates/spec/spec_helper.rb
155
+ - lib/kondate/version.rb
156
+ homepage: https://github.com/sonots/kondate
157
+ licenses:
158
+ - MIT
159
+ metadata: {}
160
+ post_install_message:
161
+ rdoc_options: []
162
+ require_paths:
163
+ - lib
164
+ required_ruby_version: !ruby/object:Gem::Requirement
165
+ requirements:
166
+ - - ">="
167
+ - !ruby/object:Gem::Version
168
+ version: '0'
169
+ required_rubygems_version: !ruby/object:Gem::Requirement
170
+ requirements:
171
+ - - ">="
172
+ - !ruby/object:Gem::Version
173
+ version: '0'
174
+ requirements: []
175
+ rubyforge_project:
176
+ rubygems_version: 2.4.5
177
+ signing_key:
178
+ specification_version: 4
179
+ summary: Kondate is yet another nodes management framework for Itamae/Serverspec.
180
+ test_files: []