head_chef 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.
Files changed (51) hide show
  1. checksums.yaml +15 -0
  2. data/.gitignore +17 -0
  3. data/.ruby-gemset +1 -0
  4. data/.ruby-version +1 -0
  5. data/Gemfile +4 -0
  6. data/LICENSE.txt +22 -0
  7. data/README.md +38 -0
  8. data/Rakefile +1 -0
  9. data/bin/head-chef +5 -0
  10. data/features/commands/diff.feature +74 -0
  11. data/features/commands/list.feature +20 -0
  12. data/features/commands/sync.feature +67 -0
  13. data/features/step_definitions/berkshelf_steps.rb +40 -0
  14. data/features/step_definitions/chef_server_steps.rb +81 -0
  15. data/features/step_definitions/git_steps.rb +3 -0
  16. data/features/support/env.rb +40 -0
  17. data/head_chef.gemspec +36 -0
  18. data/lib/head_chef/cli.rb +28 -0
  19. data/lib/head_chef/cookbook.rb +115 -0
  20. data/lib/head_chef/cookbook_diff.rb +80 -0
  21. data/lib/head_chef/tasks/diff.rb +42 -0
  22. data/lib/head_chef/tasks/env.rb +48 -0
  23. data/lib/head_chef/tasks/list.rb +17 -0
  24. data/lib/head_chef/tasks/sync.rb +38 -0
  25. data/lib/head_chef/tasks.rb +3 -0
  26. data/lib/head_chef/ui.rb +23 -0
  27. data/lib/head_chef/version.rb +3 -0
  28. data/lib/head_chef.rb +63 -0
  29. data/spec/cookbook_diff_spec.rb +77 -0
  30. data/spec/cookbook_spec.rb +60 -0
  31. data/spec/diff_spec.rb +38 -0
  32. data/spec/env_spec.rb +94 -0
  33. data/spec/fixtures/Berksfiles/default +3 -0
  34. data/spec/fixtures/Berksfiles/template.erb +5 -0
  35. data/spec/fixtures/cookbooks/test_cookbook/metadata.rb +2 -0
  36. data/spec/fixtures/cookbooks/test_cookbook/recipes/default.rb +8 -0
  37. data/spec/fixtures/cookbooks/test_cookbook_file_content_conflict/metadata.rb +2 -0
  38. data/spec/fixtures/cookbooks/test_cookbook_file_content_conflict/recipes/default.rb +10 -0
  39. data/spec/fixtures/cookbooks/test_cookbook_file_list_conflict/chefignore +2 -0
  40. data/spec/fixtures/cookbooks/test_cookbook_file_list_conflict/metadata.rb +2 -0
  41. data/spec/fixtures/cookbooks/test_cookbook_file_list_conflict/recipes/default.rb +10 -0
  42. data/spec/fixtures/cookbooks/test_cookbook_file_list_conflict/recipes/not_default.rb +1 -0
  43. data/spec/fixtures/dot_chef/head_chef.pem +27 -0
  44. data/spec/fixtures/dot_chef/knife.rb +4 -0
  45. data/spec/head_chef_spec.rb +79 -0
  46. data/spec/list_spec.rb +26 -0
  47. data/spec/spec_helper.rb +15 -0
  48. data/spec/support/chef_server.rb +100 -0
  49. data/spec/support/path_helpers.rb +38 -0
  50. data/spec/sync_spec.rb +86 -0
  51. metadata +319 -0
checksums.yaml ADDED
@@ -0,0 +1,15 @@
1
+ ---
2
+ !binary "U0hBMQ==":
3
+ metadata.gz: !binary |-
4
+ NDc2NGY2YjQxODc1ZjgwNWUzNGI5NWI5Y2YzY2ZlZDNlYzRlYWYwYQ==
5
+ data.tar.gz: !binary |-
6
+ NzA5MTRhODJiNDMwZmZlYmFjODM4NTAwMmRiNWZhZTE4N2YzZjNhYg==
7
+ SHA512:
8
+ metadata.gz: !binary |-
9
+ NTYzMDNhYTNiOTQyMDczNWQyODNlODBjNGU4MWFjNGNlMjhhYzdhYzk2OWEz
10
+ YTM5YTkzZDM4YTlmNWYxN2NlMWU5YjAyMjE2MzU2YTdlODg5NGMxMDUyMTEx
11
+ NDk5NmI1MGRjMzZkZDFiZDExY2FlZDg4NmI3OWE3OGU1NzEyY2Q=
12
+ data.tar.gz: !binary |-
13
+ NjIyMjNjODBhNmQ0OWRlNjhjMzg0ZDBkNjA1MmE5MjRhNDQ2YmRlNjViMWEx
14
+ YzJmMjg3MWQ0ODhiZDcwZWQ5NWFlZDBiMDEwNWJhNDZkM2U4NWJmZWE3NzBj
15
+ MmQ1ZmExMTZkYWNiZTcxMThmMDQ1NjU2YTU1N2QzNzZjYjg1NDQ=
data/.gitignore ADDED
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/.ruby-gemset ADDED
@@ -0,0 +1 @@
1
+ head_chef
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ 1.9.3-p286
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in head_chef.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 Medidata
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,38 @@
1
+ Head Chef
2
+ ========
3
+
4
+ The head-chef gem can be used to create environments based on the
5
+ branches in a git repository containing a master cookbook that uses
6
+ Berkshelf for dependency management.
7
+
8
+ Different Chef environments (eg. production and non-production) may
9
+ be located in different Chef organizations; this is configurable.
10
+
11
+ #Usage
12
+
13
+ To create or update the environment for the current branch:
14
+
15
+ ```bash
16
+ head-chef env sync
17
+ ```
18
+
19
+ The sync command accepts these options:
20
+
21
+ ```bash
22
+ --force, Overwrites cookbooks on chef server
23
+ ```
24
+
25
+ To compare the differences between the current branch and the
26
+ environment in use on the Chef server:
27
+
28
+ ```bash
29
+ head-chef env diff
30
+ ```
31
+
32
+ The above commands accept a number of options:
33
+
34
+ ```bash
35
+ -e <environment>, Applies to the specified Chef environment
36
+ ```
37
+
38
+
data/Rakefile ADDED
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
data/bin/head-chef ADDED
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'head_chef/cli'
4
+
5
+ HeadChef::Cli.start
@@ -0,0 +1,74 @@
1
+ Feature: head-chef env diff
2
+ Background:
3
+ Given the current git branch is named "test"
4
+ And the Chef Server has an environment named "test"
5
+
6
+ Scenario: Diff with same version constraints
7
+ Given the Berksfile has the following cookbooks:
8
+ | test_cookbook | 0.1.0 | cookbook_path |
9
+ And the environment "test" has the following cookbook version constraints:
10
+ | test_cookbook | 0.1.0 |
11
+ When I run `head-chef env diff`
12
+ Then the output should contain "identical"
13
+
14
+ Scenario: Diff with a cookbook file list conflict
15
+ Given the Berksfile has the following cookbooks:
16
+ | test_cookbook | 0.1.0 | file_list_conflict_path |
17
+ And the Chef Server has the following cookbooks uploaded:
18
+ | test_cookbook | 0.1.0 | cookbook_path |
19
+ And the environment "test" has the following cookbook version constraints:
20
+ | test_cookbook | 0.1.0 |
21
+ When I run `head-chef env diff`
22
+ Then the output should contain "CONFLICT"
23
+
24
+ Scenario: Diff with a cookbook file content conflict
25
+ Given the Berksfile has the following cookbooks:
26
+ | test_cookbook | 0.1.0 | file_content_conflict_path |
27
+ And the Chef Server has the following cookbooks uploaded:
28
+ | test_cookbook | 0.1.0 | cookbook_path |
29
+ And the environment "test" has the following cookbook version constraints:
30
+ | test_cookbook | 0.1.0 |
31
+ When I run `head-chef env diff`
32
+ Then the output should contain "CONFLICT"
33
+
34
+ Scenario: Diff with a cookbook add
35
+ Given the Berksfile has the following cookbooks:
36
+ | test_cookbook | 0.1.0 | cookbook_path |
37
+ And the environment "test" does not have the following cookbook version constraints:
38
+ | test_cookbook | 0.1.0 |
39
+ When I run `head-chef env diff`
40
+ Then the output should contain "ADD"
41
+
42
+ Scenario: Diff with a cookbook update
43
+ Given the Berksfile has the following cookbooks:
44
+ | test_cookbook | 0.1.0 | cookbook_path |
45
+ And the environment "test" has the following cookbook version constraints:
46
+ | test_cookbook | 0.0.1 |
47
+ When I run `head-chef env diff`
48
+ Then the output should contain "UPDATE"
49
+
50
+ Scenario: Diff with a cookbook remove
51
+ Given the Berksfile does not have the following cookbooks:
52
+ | test_cookbook |
53
+ And the environment "test" has the following cookbook version constraints:
54
+ | test_cookbook | 0.1.0 |
55
+ When I run `head-chef env diff`
56
+ Then the output should contain "REMOVE"
57
+
58
+ Scenario: Diff with a cookbook revert
59
+ Given the Berksfile has the following cookbooks:
60
+ | test_cookbook | 0.1.0 | cookbook_path |
61
+ And the environment "test" has the following cookbook version constraints:
62
+ | test_cookbook | 0.2.0 |
63
+ When I run `head-chef env diff`
64
+ Then the output should contain "REVERT"
65
+
66
+ Scenario: Diff using environment option with existing environment
67
+ Given the Chef Server has an environment named "environment_option"
68
+ When I run `head-chef env diff -e=environment_option`
69
+ Then the output should contain "Calculating diff"
70
+
71
+ Scenario: Diff without existing environment
72
+ Given the Chef Server does not have an environment named "test"
73
+ When I run `head-chef env diff`
74
+ Then the output should contain "not found"
@@ -0,0 +1,20 @@
1
+ Feature: head-chef env list
2
+ Background:
3
+ Given the current git branch is named "test"
4
+
5
+ Scenario: Listing cookbook and version constraints for existing Chef environment
6
+ Given the Chef Server has an environment named "test"
7
+ And the environment "test" has the following cookbook version constraints:
8
+ | cookbook | 0.0.1 |
9
+ When I run `head-chef env list`
10
+ Then the output should contain "cookbook: 0.0.1"
11
+
12
+ Scenario: Listing cookbook and version constraints for non-existent Chef environment
13
+ Given the Chef Server does not have an environment named "test"
14
+ When I run `head-chef env list`
15
+ Then the output should contain "not found"
16
+
17
+ Scenario: Calling list command with environment option
18
+ Given the Chef Server has an environment named "other_branch"
19
+ When I run `head-chef env list --environment=other_branch`
20
+ Then the output should contain "COOKBOOKS:"
@@ -0,0 +1,67 @@
1
+ Feature: head-chef env sync
2
+ Background:
3
+ Given the current git branch is named "test"
4
+
5
+ Scenario: Sync without existing chef envrionment
6
+ Given the Chef Server does not have an environment named "test"
7
+ And the Berksfile has the following cookbooks:
8
+ | test_cookbook | 0.1.0 | cookbook_path |
9
+ When I run `head-chef env sync`
10
+ Then the Chef Server should have an environment named "test"
11
+ And the environment "test" should have the following cookbook version constraints:
12
+ | test_cookbook | 0.1.0 |
13
+
14
+ Scenario: Sync with existing chef environment
15
+ Given the Chef Server has an environment named "test"
16
+ And the Berksfile has the following cookbooks:
17
+ | test_cookbook | 0.1.0 | cookbook_path |
18
+ When I run `head-chef env sync`
19
+ Then the environment "test" should have the following cookbook version constraints:
20
+ | test_cookbook | 0.1.0 |
21
+
22
+ Scenario: Sync with existing chef environment overwrites previous environment cookbook verion constraints
23
+ Given the Chef Server has an environment named "test"
24
+ And the environment "test" has the following cookbook version constraints:
25
+ | other_cookbook | 0.1.0 |
26
+ And the Berksfile has the following cookbooks:
27
+ | test_cookbook | 0.1.0 | cookbook_path |
28
+ When I run `head-chef env sync`
29
+ Then the environment "test" should have the following cookbook version constraints:
30
+ | test_cookbook | 0.1.0 |
31
+ Then the environment "test" should not have the following cookbook version constraints:
32
+ | other_cookbook | 0.1.0 |
33
+
34
+ Scenario: Sync without cookbook on Chef Server
35
+ Given the Berksfile has the following cookbooks:
36
+ | test_cookbook | 0.1.0 | cookbook_path |
37
+ And the Chef Server does not have the following cookbooks uploaded:
38
+ | test_cookbook | 0.1.0 |
39
+ When I run `head-chef env sync`
40
+ Then the Chef Server should have the following cookbooks uploaded:
41
+ | test_cookbook | 0.1.0 |
42
+
43
+ Scenario: Sync with cookbook on Chef Server
44
+ Given the Berksfile has the following cookbooks:
45
+ | test_cookbook | 0.1.0 | cookbook_path |
46
+ And the Chef Server has the following cookbooks uploaded:
47
+ | test_cookbook | 0.1.0 | cookbook_path |
48
+ When I run `head-chef env sync`
49
+ Then the Chef Server should have the following cookbooks uploaded:
50
+ | test_cookbook | 0.1.0 |
51
+
52
+ Scenario: Sync with cookbook conflict without force option
53
+ Given the Berksfile has the following cookbooks:
54
+ | test_cookbook | 0.1.0 | file_list_conflict_path |
55
+ And the Chef Server has the following cookbooks uploaded:
56
+ | test_cookbook | 0.1.0 | cookbook_path |
57
+ When I run `head-chef env sync`
58
+ Then the output should contain "conflict"
59
+
60
+ Scenario: Sync with cookbook conflict with force option
61
+ Given the Berksfile has the following cookbooks:
62
+ | test_cookbook | 0.1.0 | cookbook_path|
63
+ And the Chef Server has the following cookbooks uploaded:
64
+ | test_cookbook | 0.1.0 | cookbook_path |
65
+ When I run `head-chef env sync --force`
66
+ Then the Chef Server should have the following cookbooks uploaded:
67
+ | test_cookbook | 0.1.0 |
@@ -0,0 +1,40 @@
1
+ require 'erubis'
2
+
3
+ Given(/^the Berksfile has the following cookbooks:$/) do |cookbooks|
4
+ berksfile_template = Erubis::Eruby.new(File.read('../spec/fixtures/Berksfiles/template.erb'))
5
+
6
+
7
+ berkshelf_entries = cookbooks.raw.map do |cookbook|
8
+ cookbook_path = self.send(cookbook[2].to_sym, cookbook[0])
9
+
10
+ cookbook[2] = "path: '#{cookbook_path}'"
11
+ cookbook
12
+ end
13
+
14
+ context = {
15
+ cookbooks: berkshelf_entries
16
+ }
17
+
18
+ File.open('Berksfile', 'w') do |file|
19
+ file.write(berksfile_template.evaluate(context))
20
+ end
21
+ end
22
+
23
+
24
+ Given(/^the Berksfile does not have the following cookbooks:$/) do |cookbooks|
25
+ tmp = Tempfile.new('Berksfile.tmp')
26
+
27
+ File.open('Berksfile', 'r').each do |line|
28
+ writeable = true
29
+
30
+ cookbooks.raw.each do |name|
31
+ writeable = false if (line =~ /#{name.first}/)
32
+ end
33
+
34
+ tmp.write(line) if writeable
35
+ end
36
+
37
+ tmp.close
38
+ FileUtils.mv(tmp.path, 'Berksfile')
39
+ tmp.unlink
40
+ end
@@ -0,0 +1,81 @@
1
+ require 'ridley'
2
+ World(HeadChef::RSpec::ChefServer)
3
+
4
+ Given(/^the Chef Server has an environment named "(.*?)"$/) do |name|
5
+ chef_environment(name, { 'description' => 'This is an environment' })
6
+ end
7
+
8
+ Then(/^the Chef Server should have an environment named "(.*?)"$/) do |name|
9
+ # To avoid error warning, raise_error requires a proc
10
+ lambda { chef_server.data_store.get(['environments', name]) }.should_not raise_error
11
+ end
12
+
13
+ Given(/^the Chef Server does not have an environment named "(.*?)"$/) do |name|
14
+ if chef_server.data_store.exists?(['environments', name])
15
+ chef_server.data_store.delete(['environments', name])
16
+ end
17
+ end
18
+
19
+ Given(/^the environment "(.*?)" has the following cookbook version constraints:$/) do |name, locks|
20
+ cookbook_versions = {}.tap do |h|
21
+ locks.rows_hash.each do |cookbook, version|
22
+ h[cookbook] = version
23
+ end
24
+ end
25
+
26
+ chef_environment(name, { 'cookbook_versions' => cookbook_versions })
27
+ end
28
+
29
+ Given(/^the environment "(.*?)" does not have the following cookbook version constraints:$/) do |name, locks|
30
+ list = chef_environment_locks(name)
31
+
32
+ if list
33
+ locks.raw.each do |cookbook_name, version|
34
+ if list.key?(cookbook_name) && list[cookbook_name] == version
35
+ list.delete(cookbook_name)
36
+ end
37
+ end
38
+
39
+ chef_environment(name, { 'cookbook_versions' => list })
40
+ end
41
+ end
42
+
43
+ Then(/^the environment "(.*?)" should have the following cookbook version constraints:$/) do |name, locks|
44
+ list = chef_environment_locks(name)
45
+
46
+ locks.raw.each do |cookbook, version|
47
+ expect(list[cookbook]).to eq(version)
48
+ end
49
+ end
50
+
51
+ Then(/^the environment "(.*?)" should not have the following cookbook version constraints:$/) do |name, locks|
52
+ list = chef_environment_locks(name)
53
+
54
+ locks.raw.each do |cookbook, version|
55
+ expect(list[cookbook]).not_to eq(version)
56
+ end
57
+ end
58
+
59
+ Given(/^the Chef Server has the following cookbooks uploaded:$/) do |cookbooks|
60
+ ridley = Ridley.from_chef_config()
61
+
62
+ cookbooks.raw.each do |name, version, path|
63
+ ridley.cookbook.upload(self.send(path.to_sym, name))
64
+ end
65
+ end
66
+
67
+ Given(/^the Chef Server does not have the following cookbooks uploaded:$/) do |cookbooks|
68
+ ridley = Ridley.from_chef_config()
69
+
70
+ cookbooks.raw.each do |name, version|
71
+ ridley.cookbook.delete(name, version)
72
+ end
73
+ end
74
+
75
+ Given(/^the Chef Server should have the following cookbooks uploaded:$/) do |cookbooks|
76
+ list = chef_cookbooks
77
+
78
+ cookbooks.raw.each do |name, version|
79
+ expect(list[name]).to include(version)
80
+ end
81
+ end
@@ -0,0 +1,3 @@
1
+ Given(/^the current git branch is named "(.*?)"$/) do |name|
2
+ system "git checkout -b #{name} --quiet"
3
+ end
@@ -0,0 +1,40 @@
1
+ require 'head_chef/cli'
2
+ require 'aruba/cucumber'
3
+ Dir['spec/support/**/*.rb'].each { |f| require File.expand_path(f) }
4
+
5
+ World(HeadChef::RSpec::PathHelpers)
6
+
7
+ CHEF_SERVER_PORT = 6267
8
+
9
+ Before do
10
+ clean_tmp_path
11
+
12
+ # Set up paths
13
+ Dir.chdir(tmp_path) # ruby dir
14
+ @dirs = tmp_path # aruba dir
15
+ ENV['PWD'] = tmp_path.to_s # bash dir
16
+ ENV['BERKSHELF_PATH'] = berkshelf_path.to_s # berkshelf dir
17
+
18
+ # Write Berksfile and Berksfile.lock
19
+ FileUtils.cp fixtures_path.join('Berksfiles/default').expand_path,
20
+ tmp_path.join('Berksfile').expand_path
21
+
22
+ # Write .chef/knife.rb
23
+ FileUtils.cp_r fixtures_path.join('dot_chef').expand_path,
24
+ tmp_path.join('.chef').expand_path
25
+
26
+ # Create temp git repo
27
+ `GIT_DIR="#{tmp_path.join('.git').expand_path}" git init --quiet`
28
+
29
+ HeadChef::RSpec::ChefServer.start(port: CHEF_SERVER_PORT)
30
+
31
+ @aruba_timeout_seconds = 10
32
+ end
33
+
34
+ After do
35
+ HeadChef::RSpec::ChefServer.reset!
36
+
37
+ if File.exists?(tmp_path.join('Berksfile.lock').expand_path)
38
+ FileUtils.rm tmp_path.join('Berksfile.lock').expand_path
39
+ end
40
+ end
data/head_chef.gemspec ADDED
@@ -0,0 +1,36 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'head_chef/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = 'head_chef'
8
+ spec.version = HeadChef::VERSION
9
+ spec.authors = ['Mark Corwin']
10
+ spec.email = ['mcorwin@mdsol.com']
11
+ spec.description = %q{Head Chef is a Chef workflow CLI built on Berkshelf}
12
+ spec.summary = %q{Chef workflow tool}
13
+ spec.homepage = 'https://github.com/mdsol/head_chef'
14
+ spec.license = 'MIT'
15
+
16
+ spec.files = `git ls-files`.split($/)
17
+ spec.executables = spec.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(spec|features)/})
19
+ spec.require_paths = ['lib']
20
+
21
+ spec.add_development_dependency 'bundler', '~> 1.3'
22
+ spec.add_development_dependency 'rake'
23
+ spec.add_development_dependency 'rspec'
24
+ spec.add_development_dependency 'cucumber'
25
+ spec.add_development_dependency 'aruba'
26
+ spec.add_development_dependency 'chef-zero'
27
+ spec.add_development_dependency 'erubis'
28
+ spec.add_development_dependency 'ridley'
29
+ spec.add_development_dependency 'hashie'
30
+
31
+ spec.add_runtime_dependency 'thor'
32
+ spec.add_runtime_dependency 'grit'
33
+ spec.add_runtime_dependency 'berkshelf'
34
+ spec.add_runtime_dependency 'ridley'
35
+ spec.add_runtime_dependency 'semantic'
36
+ end
@@ -0,0 +1,28 @@
1
+ require 'head_chef'
2
+
3
+ module HeadChef
4
+ class Cli < Thor
5
+
6
+ # This is the main entry point for CLI.
7
+ # It wraps the Thor start command to provide error handling.
8
+ class << self
9
+ def start(given_args=ARGV, config={})
10
+ begin
11
+ super
12
+ HeadChef.cleanup
13
+ rescue Berkshelf::BerkshelfError => e
14
+ HeadChef.ui.error e
15
+ Kernel.exit(e.status_code)
16
+ rescue Ridley::Errors::HTTPError => e
17
+ HeadChef.ui.error "#{e.class}: #{e} \nEndpoint: #{e.env[:url]}"
18
+ Kernel.exit(1337)
19
+ end
20
+ end
21
+ end
22
+
23
+ namespace 'head_chef'
24
+
25
+ desc "env", "Sync and diff branches with Chef enviroments"
26
+ subcommand "env", Env
27
+ end
28
+ end
@@ -0,0 +1,115 @@
1
+ require 'digest'
2
+ require 'set'
3
+
4
+ module HeadChef
5
+ class Cookbook
6
+ #@TODO: add enum for methods
7
+
8
+ attr_reader :name
9
+ attr_reader :berkshelf_version # Berkshelf.lock version
10
+ attr_reader :chef_version # Chef server version
11
+
12
+ def initialize(name, berkshelf_version, chef_version)
13
+ @name = name
14
+ @berkshelf_version = berkshelf_version
15
+ @chef_version = chef_version
16
+ end
17
+
18
+ # Returns true if upload will have no conflicts, false otherwise
19
+ def diff
20
+ # Get file checksums from chef server
21
+ cookbook_resource = HeadChef.chef_server.cookbook.find(@name, @berkshelf_version)
22
+ # Cookbook not present on chef server
23
+ return true unless cookbook_resource
24
+
25
+ # Get cookbook from berkshelf cookbook cache
26
+ # NOTE: requires berks install/update to be run to ensure cache is
27
+ # populate from lockfile
28
+ # NOTE: Berkshelf 3.0 can load cookbooks from lockfile without loading
29
+ # cache
30
+ # User manage berksfile? For now, yes
31
+ berkshelf_cookbook = HeadChef.berksfile.cached_cookbooks.find do |cb|
32
+ cb.name == "#{@name}-#{@berkshelf_version}"
33
+ end
34
+ # Cookbook will not be uploaded since it is not in Berksfile.lock
35
+ return true unless berkshelf_cookbook
36
+
37
+ cookbook_files_mashes = files_from_manifest(cookbook_resource.manifest)
38
+
39
+ # Diff file lists
40
+ cookbook_files = Set.new(cookbook_files_mashes.map(&:path))
41
+ berkshelf_files = Set.new(remove_ignored_files(berkshelf_cookbook.path))
42
+
43
+ return false unless berkshelf_files == cookbook_files
44
+
45
+ # Diff file contents
46
+ cookbook_files_mashes.each do |cookbook_file_mash|
47
+ berkshelf_cookbook_file = "#{berkshelf_cookbook.path}/#{cookbook_file_mash.path}"
48
+ berkshelf_cookbook_checksum = checksum_file(berkshelf_cookbook_file) if File.exists?(berkshelf_cookbook_file)
49
+
50
+ return false unless berkshelf_cookbook_checksum == cookbook_file_mash.checksum
51
+ end
52
+
53
+ true
54
+ end
55
+
56
+ def to_s
57
+ if @chef_version && @berkshelf_version && (@chef_version != @berkshelf_version)
58
+ "#{@name}: #{@chef_version} => #{@berkshelf_version}"
59
+ else
60
+ "#{@name}: #{@chef_version || @berkshelf_version}"
61
+ end
62
+ end
63
+
64
+ private
65
+
66
+ def files_from_manifest(manifest)
67
+ manifest.values.flatten
68
+ end
69
+
70
+ # Taken from Chef
71
+ def checksum_file(file)
72
+ File.open(file, 'rb') { |f| checksum_io(f, Digest::MD5.new) }
73
+ end
74
+
75
+ def checksum_io(io, digest)
76
+ while chunk = io.read(1024 * 8)
77
+ digest.update(chunk)
78
+ end
79
+ digest.hexdigest
80
+ end
81
+
82
+ def remove_ignored_files(path)
83
+ file_list = Dir.chdir(path) do
84
+ Dir['**/*'].select { |f| File.file?(f) }
85
+ end
86
+
87
+ ignore_file = File.join(path, 'chefignore')
88
+ ignore_globs = parse_ignore_file(ignore_file)
89
+
90
+ remove_ignores_from(file_list, ignore_globs)
91
+ end
92
+
93
+ def remove_ignores_from(file_list, ignore_globs)
94
+ file_list.reject do |file|
95
+ ignored?(file, ignore_globs)
96
+ end
97
+ end
98
+
99
+ def ignored?(file_name, ignores)
100
+ ignores.any? { |glob| File.fnmatch?(glob, file_name) }
101
+ end
102
+
103
+ def parse_ignore_file(ignore_file)
104
+ [].tap do |ignore_globs|
105
+ if File.exist?(ignore_file) && File.readable?(ignore_file) &&
106
+ (File.file?(ignore_file) || File.symlink?(ignore_file))
107
+ File.foreach(ignore_file) do |line|
108
+ ignore_globs << line.strip unless line =~ /^\s*(?:#.*)?$/
109
+ end
110
+ end
111
+ end
112
+ end
113
+
114
+ end
115
+ end