bourdain 1.2.13

Sign up to get free protection for your applications and to get access to all the features.
Files changed (51) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +12 -0
  3. data/Gemfile +11 -0
  4. data/Rakefile +19 -0
  5. data/Readme.md +1 -0
  6. data/TODO.md +41 -0
  7. data/VERSION +1 -0
  8. data/bin/tony +54 -0
  9. data/bin/tony-bump +3 -0
  10. data/bin/tony-check +3 -0
  11. data/bin/tony-generate +3 -0
  12. data/bourdain.gemspec +25 -0
  13. data/install-or-upgrade.sh +4 -0
  14. data/lib/bourdain/helpers/config.rb +12 -0
  15. data/lib/bourdain/helpers/locals.rb +17 -0
  16. data/lib/bourdain/helpers/logger.rb +41 -0
  17. data/lib/bourdain/helpers/parser.rb +76 -0
  18. data/lib/bourdain/helpers/registry.rb +53 -0
  19. data/lib/bourdain/helpers.rb +32 -0
  20. data/lib/bourdain/metadata.rb +19 -0
  21. data/lib/bourdain/resources/checks/bourdain.rb +71 -0
  22. data/lib/bourdain/resources/checks/chef.rb +35 -0
  23. data/lib/bourdain/resources/checks/cookbooks.rb +97 -0
  24. data/lib/bourdain/resources/checks/hooks.rb +67 -0
  25. data/lib/bourdain/resources/checks/ssh_config.rb +61 -0
  26. data/lib/bourdain/resources/checks.rb +36 -0
  27. data/lib/bourdain/resources/commands/bump.rb +74 -0
  28. data/lib/bourdain/resources/commands/check.rb +103 -0
  29. data/lib/bourdain/resources/commands/generate.rb +33 -0
  30. data/lib/bourdain/resources/commands.rb +10 -0
  31. data/lib/bourdain/resources/generators/attribute.rb +43 -0
  32. data/lib/bourdain/resources/generators/cookbook.rb +125 -0
  33. data/lib/bourdain/resources/generators/recipe.rb +54 -0
  34. data/lib/bourdain/resources/generators.rb +22 -0
  35. data/lib/bourdain/resources.rb +104 -0
  36. data/lib/bourdain.rb +3 -0
  37. data/templates/chef/environment.json +8 -0
  38. data/templates/chef/role.json +9 -0
  39. data/templates/cookbook/Berksfile +3 -0
  40. data/templates/cookbook/Readme.md +1 -0
  41. data/templates/cookbook/VERSION +1 -0
  42. data/templates/cookbook/Vagrantfile +19 -0
  43. data/templates/cookbook/attributes/example.rb +24 -0
  44. data/templates/cookbook/busser_minitest.rb +7 -0
  45. data/templates/cookbook/gitignore +26 -0
  46. data/templates/cookbook/kitchen.yml +23 -0
  47. data/templates/cookbook/metadata.rb +10 -0
  48. data/templates/cookbook/pre-commit +80 -0
  49. data/templates/cookbook/recipes/example.rb +38 -0
  50. data/test.sh +12 -0
  51. metadata +165 -0
@@ -0,0 +1,67 @@
1
+ module Bourdain
2
+ module Checks
3
+
4
+ class HooksCheck < Check
5
+ usage :check, <<-END
6
+ Check the local copy of Kitchen hooks
7
+ hooks
8
+ END
9
+
10
+
11
+ def initialize _
12
+ super []
13
+ return unless require_chef!
14
+ check_hooks!
15
+ end
16
+
17
+
18
+
19
+ private
20
+ def check_hooks!
21
+ # Ensure we have an up-to-date copy of Kitchen hooks
22
+ hooks = File.join('utils', 'hooks')
23
+
24
+ ahead_of_remote = false
25
+ unless Dir.exists? hooks
26
+ log.warn "Hm, I don't see a copy of Kitchen hooks. I'll fix that for you..."
27
+ %x| git clone git@git.bluejeansnet.com:kitchen-hooks.git #{hooks} |
28
+ log.info "Your copy of Kitchen hooks is now up-to-date."
29
+
30
+ else
31
+ if youre_dirty? hooks
32
+ log.warn "Your copy of Kitchen hooks is dirty."
33
+ elsif youre_ahead? hooks
34
+ log.warn "Your copy of Kitchen hooks is ahead of the remote."
35
+ ahead_of_remote = true
36
+ elsif youre_behind? hooks
37
+ log.warn "Hey, looks like your copy of Kitchen hooks is out-of-date. I'll fix that for you..."
38
+ Dir.chdir(bourdain) { %x| git pull | }
39
+ log.info "Your copy of Kitchen hooks is now up-to-date."
40
+ else
41
+ log.info "Your copy of Kitchen hooks looks up-to-date."
42
+ end
43
+ end
44
+
45
+ matches = File.read('Gemfile.lock').match(/kitchen_hooks \((.*?)\)$/)
46
+
47
+ if matches.nil?
48
+ log.warn "Couldn't find Kitchen hooks in your Gemfile lock. Something is amiss..."
49
+ error!
50
+
51
+ else
52
+ repo_version = matches[1].strip
53
+ local_version = File.read(File.join(hooks, 'VERSION')).strip
54
+
55
+ if repo_version == local_version
56
+ log.info "Your Chef repo is bundled with the latest Kitchen hooks."
57
+ elsif ahead_of_remote
58
+ # Don't worry about it, we've already warned them
59
+ elsif local_version < repo_version
60
+ log.error 'Your Chef repo is bundled with an older Kitchen hooks. Try "bundle update".'
61
+ error!
62
+ end
63
+ end
64
+ end
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,61 @@
1
+ require 'json'
2
+
3
+ module Bourdain
4
+ module Checks
5
+
6
+ class SSHConfigCheck < Check
7
+ usage :check, <<-END
8
+ Ensure all nodes are enterened into the local SSH config
9
+ ssh_config
10
+ END
11
+
12
+
13
+ def initialize cookbook_config
14
+ super []
15
+ return unless require_chef!
16
+ check_ssh_config!
17
+ end
18
+
19
+
20
+
21
+ private
22
+ def check_ssh_config! ssh_config=File.join(ENV['HOME'], '.ssh', 'config')
23
+ unless File::exist?(ssh_config)
24
+ log.warn 'Skipping SSH config check. File does not exist: %s' % ssh_config.inspect
25
+ return
26
+ end
27
+
28
+ nodes = Dir[File.join('nodes', '*.json')]
29
+ nodes = nodes.map do |path|
30
+ JSON::parse File.read(path)
31
+ end
32
+
33
+ config = File.read(ssh_config)
34
+
35
+ hosts = []
36
+ config.lines.select do |l|
37
+ l =~ /Host (\S+)/
38
+ hosts << $1 if $1
39
+ end
40
+
41
+ unconfigured_nodes = nodes.select do |n|
42
+ !hosts.include? n['name']
43
+ end
44
+
45
+ File.open(ssh_config, 'a') do |f|
46
+ f.puts unless unconfigured_nodes.empty?
47
+ unconfigured_nodes.each do |n|
48
+ log.info 'Adding %s to %s' % [ n['name'].inspect, ssh_config ]
49
+ f.puts "Host %s" % n['name']
50
+ f.puts "\tHostName\t%s" % n['name']
51
+ f.puts "\tPort\t22"
52
+ f.puts "\tUser\tvagrant"
53
+ f.puts "\tStrictHostKeyChecking\tno"
54
+ f.puts "\tIdentityFile\t~/.vagrant.d/insecure_private_key"
55
+ f.puts
56
+ end
57
+ end
58
+ end
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,36 @@
1
+ require 'erb'
2
+ require 'fileutils'
3
+
4
+
5
+ module Bourdain
6
+ module Checks
7
+ class Check < Bourdain::Resource
8
+ protected
9
+ def work_tree repo
10
+ "--work-tree=#{File.join(Dir.pwd, repo)}"
11
+ end
12
+
13
+ def git_dir repo
14
+ "--git-dir=#{File.join(Dir.pwd, repo, '.git')}"
15
+ end
16
+
17
+ def youre_behind? repo
18
+ `git #{git_dir repo} log ..origin/master --oneline`.split("\n").length > 0
19
+ end
20
+
21
+ def youre_ahead? repo
22
+ `git #{git_dir repo} log origin/master.. --oneline`.split("\n").length > 0
23
+ end
24
+
25
+ def youre_dirty? repo
26
+ `git #{git_dir repo} #{work_tree repo} diff HEAD --numstat`.split("\n").length > 0
27
+ end
28
+ end
29
+ end
30
+ end
31
+
32
+ require_relative 'checks/chef'
33
+ require_relative 'checks/hooks'
34
+ require_relative 'checks/bourdain'
35
+ require_relative 'checks/cookbooks'
36
+ require_relative 'checks/ssh_config'
@@ -0,0 +1,74 @@
1
+ module Bourdain
2
+ module Commands
3
+
4
+ class BumpCommand < Command
5
+ usage :command, <<-END
6
+ Bump a VERSION file (patch by default)
7
+ bump [<options>] [<version-spec=major|minor|patch|x.y.z>]
8
+ END
9
+
10
+ def initialize argv
11
+ super
12
+ version_spec = argv.shift
13
+ version_spec = 'patch' if version_spec.nil?
14
+ verify! version_spec
15
+ return unless require_versionfile!
16
+ current_version = get_cookbook_version
17
+ new_version = bump_version version_spec, current_version
18
+ set_cookbook_version new_version
19
+ log.info "Bumped to #{new_version}."
20
+ end
21
+
22
+
23
+ private
24
+ def set_cookbook_version version
25
+ File.open('VERSION', 'w') { |f| f.write version }
26
+ end
27
+
28
+ def get_cookbook_version
29
+ File.open('VERSION').read.strip
30
+ end
31
+
32
+ def verify! version_spec
33
+ case version_spec
34
+ when /major/
35
+ :major
36
+ when /minor/
37
+ :minor
38
+ when /patch/
39
+ :patch
40
+ when /\d+\.\d+\.\d+/
41
+ :manual
42
+ else
43
+ Trollop::die 'Invalid <version-spec> provided'
44
+ end
45
+ end
46
+
47
+ def bump_version version_spec, version
48
+ type = verify! version_spec
49
+
50
+ # I'm not really that thinky, so I stole this from knife-spork:
51
+ #
52
+ # https://github.com/jonlives/knife-spork/blob/master/lib/chef/knife/spork-bump.rb
53
+ #
54
+ if type == :manual
55
+ version_array = version_spec.split('.')
56
+
57
+ else
58
+ field = case type
59
+ when :patch ; 2
60
+ when :minor ; 1
61
+ when :major ; 0
62
+ end
63
+
64
+ version_array = version.split('.').map(&:to_i)
65
+ version_array[field] += 1
66
+ ((field + 1)..2).each { |i| version_array[i] = 0 }
67
+ end
68
+
69
+ version_array.join('.')
70
+ end
71
+ end
72
+
73
+ end
74
+ end
@@ -0,0 +1,103 @@
1
+ require 'json'
2
+ require 'fileutils'
3
+
4
+ require 'pmap'
5
+ require 'gitlab'
6
+
7
+
8
+ module Bourdain
9
+ module Commands
10
+
11
+ class CheckCommand < Command
12
+ usage :command, <<-END
13
+ Check the state of the Kitchen, Bourdain and other things
14
+ check [<options>] [<check>]
15
+ <check>
16
+ END
17
+
18
+
19
+
20
+ def initialize argv
21
+ super
22
+ checks = Bourdain::Registry.specs('check')
23
+ maybe_check_position = checks.map { |c| argv.index c[:name].to_s }.compact.shift
24
+ check_position = maybe_check_position.nil? ? argv.length : maybe_check_position
25
+
26
+ options = argv.slice(0, check_position)
27
+ argv = argv.slice(check_position, argv.length)
28
+
29
+ super options
30
+
31
+ begin
32
+ gitlab_token = Bourdain::Config.items[:gitlab][:token]
33
+ rescue
34
+ log.fatal "Couldn't find your GitLab token in your config, bailing!"
35
+ error!
36
+ return
37
+ end
38
+
39
+ Gitlab.configure do |gitlab|
40
+ gitlab.endpoint = "http://#{Bourdain::GITLAB_HOST}/api/v3"
41
+ gitlab.private_token = gitlab_token
42
+ end
43
+
44
+ check = argv.shift
45
+
46
+ unless check.nil?
47
+ check = Bourdain::Registry.klass('check', check)
48
+ Trollop::die 'Invalid <check> provided' if check.nil?
49
+ end
50
+
51
+ return unless require_chef!
52
+
53
+ if check.nil?
54
+ @status = Bourdain::Registry.map('check') do |c|
55
+ c.new(cookbook_config_from_gitlab).status
56
+ end.sort.last
57
+ else
58
+ @status = check.new(cookbook_config_from_gitlab).status
59
+ end
60
+ end
61
+
62
+
63
+
64
+ private
65
+ def cookbook_config_from_gitlab
66
+ cookbook_configs = []
67
+
68
+ # Have to page results as GitLab absolutely will not return more than
69
+ # 100 results per page (even if we ask for more)
70
+ 1.upto(25) do |page|
71
+ Gitlab.projects(per_page: 50, page: page).each do |project|
72
+ namespace, name = project.name_with_namespace.split(' / ', 2)
73
+ namespace = nil if name.nil?
74
+ next unless namespace == 'chef'
75
+ next if %w[ chef kitchen bourdain ].include? name
76
+
77
+ cookbook_name = name.sub(/^\w+?_/, '')
78
+ cookbook_path = name.sub(/^(\w+?)_/, '\1s/bjn_')
79
+
80
+ # Fork names shouldn't have the bjn_ prefix
81
+ if name =~ /^fork_/
82
+ cookbook_name.sub!(/(bjn|fork)_/, '')
83
+ cookbook_path.sub!(/(bjn|fork)_/, '')
84
+ end
85
+
86
+ name =~ /^(\w+?)_/
87
+ cookbook_type = $1.to_sym
88
+
89
+ cookbook_configs << {
90
+ name: cookbook_name,
91
+ type: cookbook_type,
92
+ path: cookbook_path,
93
+ repo: "#{namespace}/#{name}.git",
94
+ project: project
95
+ }
96
+ end
97
+ end
98
+
99
+ cookbook_configs
100
+ end
101
+ end
102
+ end
103
+ end
@@ -0,0 +1,33 @@
1
+ module Bourdain
2
+ module Commands
3
+
4
+ class GenerateCommand < Command
5
+ usage :command, <<-END
6
+ Generate various things. Take that, Chef DK!
7
+ generate [<options>] <generator>
8
+ <generator>
9
+ END
10
+
11
+ def initialize argv
12
+ generators = Bourdain::Registry.specs('generator')
13
+ maybe_generator_position = generators.map { |c| argv.index c[:name].to_s }.compact.shift
14
+ generator_position = maybe_generator_position.nil? ? argv.length : maybe_generator_position
15
+ generator_name = argv[generator_position]
16
+
17
+ options = argv.slice(0, generator_position)
18
+ argv = argv.slice(generator_position, argv.length)
19
+
20
+ super options
21
+
22
+ generator = nil
23
+ generator = argv.shift unless maybe_generator_position.nil?
24
+ Trollop::die 'Invalid <generator> provided' if generator.nil?
25
+ generator = Bourdain::Registry.klass('generator', generator_name)
26
+ Trollop::die 'Invalid <generator> provided' if generator.nil?
27
+
28
+ @status = generator.new(argv).status
29
+ end
30
+ end
31
+
32
+ end
33
+ end
@@ -0,0 +1,10 @@
1
+ module Bourdain
2
+ module Commands
3
+ class Command < Bourdain::Resource
4
+ end
5
+ end
6
+ end
7
+
8
+ require_relative 'commands/bump'
9
+ require_relative 'commands/check'
10
+ require_relative 'commands/generate'
@@ -0,0 +1,43 @@
1
+ module Bourdain
2
+ module Generators
3
+
4
+ class AttributeGenerator < Generator
5
+ usage :generator, <<-END
6
+ Generate a new attribute file in the current cookbook
7
+ attribute [<options>] <name>
8
+ END
9
+
10
+
11
+ def initialize argv
12
+ super
13
+
14
+ name = argv.shift
15
+ Trollop::die 'No <name> provided' if name.nil?
16
+ Trollop::die 'Invalid <name> provided' unless valid? name
17
+ name = normalized(name)
18
+
19
+ return unless require_cookbook!
20
+
21
+ FileUtils.mkdir_p 'attributes'
22
+ path = File.join('attributes', name + '.rb')
23
+
24
+ if File::exists? path
25
+ log.warn "Attribute file already exists. Doing nothing."
26
+ return
27
+ end
28
+
29
+ cookbook_name = File.basename Dir.pwd
30
+
31
+ apply_template path, \
32
+ template: %w[ cookbook attributes example.rb ],
33
+ locals: {
34
+ cookbook_name: cookbook_name,
35
+ name: name
36
+ }
37
+
38
+ log.info "Generated attribute file at #{path}"
39
+ end
40
+ end
41
+
42
+ end
43
+ end
@@ -0,0 +1,125 @@
1
+ require 'trollop'
2
+
3
+
4
+ module Bourdain
5
+ module Generators
6
+
7
+ class CookbookGenerator < Generator
8
+ usage :generator, <<-END
9
+ Generate a new cookbook in the current repo
10
+ cookbook [<options>] <path>
11
+ --dot-chef (-c) :: Generate a .chef directory (default: false)
12
+ --git-init :: Initialize a git repository (default: true)
13
+ --attributes :: Generate a default attribute file (default: true)
14
+ --recipe :: Generate a default recipe (default: true)
15
+ --berksfile :: Generate a Berksfile (default: true)
16
+ --gitignore (-i) :: Generate a .gitignore (default: true)
17
+ --metadata :: Generate cookbook metadata (default: true)
18
+ --readme (-m) :: Generate a Readme (default: true)
19
+ --kitchenfile :: Generate a Test Kitchen file (default: true)
20
+ --vagrantfile :: Generate a Vagrantfile (default: true)
21
+ --versionfile (-z) :: Generate a VERSION file (default: true)
22
+ --pre-commit :: Generate a pre-commit hook (default: true)
23
+ --description (-p) <s> :: Description for resources
24
+ END
25
+
26
+ def initialize argv
27
+ super
28
+
29
+ path = argv.shift
30
+ Trollop::die 'No <path> provided' if path.nil?
31
+ Trollop::die 'Invalid <path> provided' unless valid? path
32
+
33
+ return unless require_chef!
34
+
35
+ if Dir::exists? path
36
+ log.warn "Cookbook already exists. Doing nothing."
37
+ return
38
+ end
39
+
40
+ name = File.basename(normalized(path))
41
+
42
+ FileUtils.mkdir_p path
43
+
44
+ if opts[:dot_chef]
45
+ FileUtils.mkdir_p File.join(path, '.chef/data_bags')
46
+ FileUtils.mkdir_p File.join(path, '.chef/nodes')
47
+ end
48
+
49
+ if opts[:attributes]
50
+ FileUtils.mkdir_p File.join(path, 'attributes')
51
+ apply_template File.join(path, 'attributes', 'default.rb'), \
52
+ template: %w[ cookbook attributes example.rb ], locals: {
53
+ name: 'default', cookbook_name: name
54
+ }
55
+ end
56
+
57
+ if opts[:recipe]
58
+ FileUtils.mkdir_p File.join(path, 'recipes')
59
+ apply_template File.join(path, 'recipes', 'default.rb'), \
60
+ template: %w[ cookbook recipes example.rb ], locals: {
61
+ name: 'default', cookbook_name: name
62
+ }
63
+ end
64
+
65
+ if opts[:berksfile]
66
+ apply_template File.join(path, 'Berksfile'), \
67
+ template: %w[ cookbook Berksfile ]
68
+ end
69
+
70
+ if opts[:gitignore]
71
+ apply_template File.join(path, '.gitignore'), \
72
+ template: %w[ cookbook gitignore ]
73
+ end
74
+
75
+ if opts[:metadata]
76
+ apply_template File.join(path, 'metadata.rb'), \
77
+ template: %w[ cookbook metadata.rb ], locals: { name: name }
78
+ end
79
+
80
+ if opts[:readme]
81
+ apply_template File.join(path, 'Readme.md'), \
82
+ template: %w[ cookbook Readme.md ], locals: { name: name }
83
+ end
84
+
85
+ if opts[:kitchenfile]
86
+ minitest_path = File.join(path, 'test', 'integration', 'default', 'minitest')
87
+ FileUtils.mkdir_p minitest_path
88
+ apply_template File.join(minitest_path, 'test_default.rb'), \
89
+ template: %w[ cookbook busser_minitest.rb ], locals: {
90
+ cookbook_name: name, recipe_name: 'default'
91
+ }
92
+ apply_template File.join(path, '.kitchen.yml'), \
93
+ template: %w[ cookbook kitchen.yml ], locals: { name: name }
94
+ end
95
+
96
+ if opts[:vagrantfile]
97
+ apply_template File.join(path, 'Vagrantfile'), \
98
+ template: %w[ cookbook Vagrantfile ], locals: { name: name }
99
+ end
100
+
101
+ if opts[:versionfile]
102
+ apply_template File.join(path, 'VERSION'), \
103
+ template: %w[ cookbook VERSION ]
104
+ end
105
+
106
+ if opts[:pre_commit]
107
+ hooks = File.join(path, '.git', 'hooks')
108
+ FileUtils.mkdir_p hooks
109
+ pre_commit_hook = File.join(hooks, 'pre-commit')
110
+ apply_template pre_commit_hook, template: %w[ cookbook pre-commit ]
111
+ FileUtils.chmod 0755, pre_commit_hook
112
+ end
113
+
114
+ if opts[:git_init]
115
+ Dir.chdir(path) do
116
+ `git init .`
117
+ end
118
+ end
119
+
120
+ log.info "Generated cookbook at #{path}."
121
+ end
122
+ end
123
+
124
+ end
125
+ end
@@ -0,0 +1,54 @@
1
+ module Bourdain
2
+ module Generators
3
+
4
+ class RecipeGenerator < Generator
5
+ usage :generator, <<-END
6
+ Generate a new recipe in the current cookbook
7
+ recipe [<options>] <name>
8
+ --minitest :: Generate a minitest file (default: true)
9
+ END
10
+
11
+
12
+ def initialize argv
13
+ super
14
+
15
+ name = argv.shift
16
+ Trollop::die 'No <name> provided' if name.nil?
17
+ Trollop::die 'Invalid <name> provided' unless valid? name
18
+ name = normalized(name)
19
+
20
+ return unless require_cookbook!
21
+
22
+ FileUtils.mkdir_p 'recipes'
23
+ path = File.join('recipes', name + '.rb')
24
+
25
+ if File::exists? path
26
+ log.warn 'Recipe already exists. Doing nothing.'
27
+ return
28
+ end
29
+
30
+ cookbook_name = File.basename Dir.pwd
31
+
32
+ apply_template path, \
33
+ template: %w[ cookbook recipes example.rb ],
34
+ locals: {
35
+ cookbook_name: cookbook_name,
36
+ name: name
37
+ }
38
+
39
+ if opts[:minitest]
40
+ minitest_path = File.join('test', 'integration', 'default', 'minitest', "test_#{name}.rb")
41
+ FileUtils.mkdir_p File.dirname(minitest_path)
42
+ apply_template minitest_path, \
43
+ template: %w[ cookbook busser_minitest.rb ], locals: {
44
+ cookbook_name: cookbook_name, recipe_name: name
45
+ }
46
+ log.info "Generated test for recipe at #{minitest_path}."
47
+ end
48
+
49
+ log.info "Generated recipe at #{path}"
50
+ end
51
+ end
52
+
53
+ end
54
+ end
@@ -0,0 +1,22 @@
1
+ require 'erb'
2
+ require 'fileutils'
3
+
4
+
5
+ module Bourdain
6
+ module Generators
7
+ class Generator < Bourdain::Resource
8
+ protected
9
+ def valid? name
10
+ name =~ /[_a-z]+/
11
+ end
12
+
13
+ def normalized name
14
+ File::basename(name, '.rb')
15
+ end
16
+ end
17
+ end
18
+ end
19
+
20
+ require_relative 'generators/attribute'
21
+ require_relative 'generators/cookbook'
22
+ require_relative 'generators/recipe'