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.
- checksums.yaml +15 -0
- data/.gitignore +17 -0
- data/.ruby-gemset +1 -0
- data/.ruby-version +1 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +38 -0
- data/Rakefile +1 -0
- data/bin/head-chef +5 -0
- data/features/commands/diff.feature +74 -0
- data/features/commands/list.feature +20 -0
- data/features/commands/sync.feature +67 -0
- data/features/step_definitions/berkshelf_steps.rb +40 -0
- data/features/step_definitions/chef_server_steps.rb +81 -0
- data/features/step_definitions/git_steps.rb +3 -0
- data/features/support/env.rb +40 -0
- data/head_chef.gemspec +36 -0
- data/lib/head_chef/cli.rb +28 -0
- data/lib/head_chef/cookbook.rb +115 -0
- data/lib/head_chef/cookbook_diff.rb +80 -0
- data/lib/head_chef/tasks/diff.rb +42 -0
- data/lib/head_chef/tasks/env.rb +48 -0
- data/lib/head_chef/tasks/list.rb +17 -0
- data/lib/head_chef/tasks/sync.rb +38 -0
- data/lib/head_chef/tasks.rb +3 -0
- data/lib/head_chef/ui.rb +23 -0
- data/lib/head_chef/version.rb +3 -0
- data/lib/head_chef.rb +63 -0
- data/spec/cookbook_diff_spec.rb +77 -0
- data/spec/cookbook_spec.rb +60 -0
- data/spec/diff_spec.rb +38 -0
- data/spec/env_spec.rb +94 -0
- data/spec/fixtures/Berksfiles/default +3 -0
- data/spec/fixtures/Berksfiles/template.erb +5 -0
- data/spec/fixtures/cookbooks/test_cookbook/metadata.rb +2 -0
- data/spec/fixtures/cookbooks/test_cookbook/recipes/default.rb +8 -0
- data/spec/fixtures/cookbooks/test_cookbook_file_content_conflict/metadata.rb +2 -0
- data/spec/fixtures/cookbooks/test_cookbook_file_content_conflict/recipes/default.rb +10 -0
- data/spec/fixtures/cookbooks/test_cookbook_file_list_conflict/chefignore +2 -0
- data/spec/fixtures/cookbooks/test_cookbook_file_list_conflict/metadata.rb +2 -0
- data/spec/fixtures/cookbooks/test_cookbook_file_list_conflict/recipes/default.rb +10 -0
- data/spec/fixtures/cookbooks/test_cookbook_file_list_conflict/recipes/not_default.rb +1 -0
- data/spec/fixtures/dot_chef/head_chef.pem +27 -0
- data/spec/fixtures/dot_chef/knife.rb +4 -0
- data/spec/head_chef_spec.rb +79 -0
- data/spec/list_spec.rb +26 -0
- data/spec/spec_helper.rb +15 -0
- data/spec/support/chef_server.rb +100 -0
- data/spec/support/path_helpers.rb +38 -0
- data/spec/sync_spec.rb +86 -0
- 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
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
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,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,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
|