head_chef 0.1.0

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 +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