chef_synchronize 1.0.7
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 +7 -0
- data/LICENSE.txt +22 -0
- data/README.md +66 -0
- data/bin/chef_sync +27 -0
- data/lib/chef_sync.rb +82 -0
- data/lib/chef_sync/chef_component.rb +117 -0
- data/lib/chef_sync/chef_component/cookbook.rb +99 -0
- data/lib/chef_sync/chef_component/data_bag_item.rb +35 -0
- data/lib/chef_sync/chef_component/environment.rb +5 -0
- data/lib/chef_sync/chef_component/role.rb +5 -0
- data/lib/chef_sync/knife.rb +113 -0
- data/lib/chef_sync/version.rb +3 -0
- data/spec/chef_sync/chef_component/cookbook_spec.rb +117 -0
- data/spec/chef_sync/chef_component/data_bag_item_spec.rb +25 -0
- data/spec/chef_sync/chef_component/environment_spec.rb +28 -0
- data/spec/chef_sync/chef_component/role_spec.rb +29 -0
- data/spec/chef_sync/chef_component_shared_behaviors.rb +86 -0
- data/spec/chef_sync/chef_component_spec.rb +37 -0
- data/spec/chef_sync_spec.rb +34 -0
- data/spec/spec_helper.rb +53 -0
- metadata +185 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 72b4321e9cf345928e25d635dac34f33686cf6c6
|
4
|
+
data.tar.gz: 4ecd34bd2ff2b46c35a39e20df415cd680d5f25d
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 5ac32717abb2b45d076748c766b7c2e6a3bfbf43aefe273576841583380f7b88c2e377867e04db663f5a1688e03ac4fd14ea87c170866dae2e08acaf2d42b36b
|
7
|
+
data.tar.gz: 906c3f0d0b5a0be5fa2d9654023b90dbdd249e054cccc2d2f8d7830bbbfe48189cf79753a84c08b8ee01fde0b1d8df46e7df86b04228ce810c69ade786c6fe78
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2016 Cozy Services Ltd.
|
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,66 @@
|
|
1
|
+
# ChefSync
|
2
|
+
|
3
|
+
Sync a monolithic chef repo to a chef server.
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
Add this line to your chef repo's Gemfile:
|
8
|
+
|
9
|
+
```ruby
|
10
|
+
gem 'chef_synchronize'
|
11
|
+
```
|
12
|
+
|
13
|
+
And then execute:
|
14
|
+
|
15
|
+
$ bundle install
|
16
|
+
|
17
|
+
Or install it yourself as:
|
18
|
+
|
19
|
+
$ gem install chef_synchronize
|
20
|
+
|
21
|
+
## Configuration
|
22
|
+
|
23
|
+
`chef_synchronize` requires configuration to post to Slack (only required if you want
|
24
|
+
to post to Slack) and to communicate with the Chef server via Knife and Ridley.
|
25
|
+
|
26
|
+
To configure Slack, you must set the `CHEFSYNC_SLACK_WEBHOOK_URL` environment
|
27
|
+
variable. You can optionally also set `CHEFSYNC_SLACK_USERNAME` to set the
|
28
|
+
username you'd like to post to Slack under, and `CHEFSYNC_SLACK_CHANNEL` to set
|
29
|
+
the Slack channel.
|
30
|
+
|
31
|
+
You can also optionally set `CHEFSYNC_CI_BUILD_URL` and `CHEFSYNC_COMMIT_URL`
|
32
|
+
environment variables. If you set both, they will appear as links in the Slack
|
33
|
+
post's pretext above the results of the sync.
|
34
|
+
|
35
|
+
To configure Knife/Ridley, you must have a `.chef` directory in your PATH that
|
36
|
+
contains a `.knife.rb` config file.
|
37
|
+
|
38
|
+
## Usage
|
39
|
+
|
40
|
+
From within your chef repo, execute the following line to see a list of
|
41
|
+
unsynced changes:
|
42
|
+
|
43
|
+
$ bundle exec chef_sync
|
44
|
+
|
45
|
+
By default, `chef_sync` is set to dryrun mode, where `chef_sync` will tell you
|
46
|
+
what updates would happen without actually syncing things to the Chef server,
|
47
|
+
and to avoid posting to Slack. The output will be printed to the console
|
48
|
+
regardless of whether it's set to post to Slack.
|
49
|
+
|
50
|
+
$ bundle exec chef_sync --help
|
51
|
+
# help menu
|
52
|
+
$ bundle exec chef_sync --no-dryrun
|
53
|
+
# runs chef_sync and actually syncs changes to Chef server
|
54
|
+
$ bundle exec chef_sync -p
|
55
|
+
# runs chef_sync and posts output to Slack in addition to in the console
|
56
|
+
|
57
|
+
## Contributing
|
58
|
+
|
59
|
+
1. Fork it ( https://github.com/[my-github-username]/chef_synchronize/fork )
|
60
|
+
2. Install dependencies (`bundle install`)
|
61
|
+
3. Create your feature branch (`git checkout -b my-new-feature`)
|
62
|
+
4. Make your changes.
|
63
|
+
5. Run the tests and make sure they pass (`bundle exec rspec`)
|
64
|
+
6. Commit your changes (`git commit -am 'Add some feature'`)
|
65
|
+
7. Push to the branch (`git push origin my-new-feature`)
|
66
|
+
8. Create a new pull request.
|
data/bin/chef_sync
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
lib = File.expand_path('../../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
|
5
|
+
require 'optparse'
|
6
|
+
require 'chef_sync'
|
7
|
+
|
8
|
+
options = {
|
9
|
+
dryrun: true,
|
10
|
+
post_to_slack: false
|
11
|
+
}
|
12
|
+
|
13
|
+
OptionParser.new do |opts|
|
14
|
+
opts.banner = 'Usage: chef-sync [options]'
|
15
|
+
|
16
|
+
opts.on('-d', '--[no-]dryrun',
|
17
|
+
'Print out changes without actually syncing to chef server (default: true)') do |d|
|
18
|
+
options[:dryrun] = d
|
19
|
+
end
|
20
|
+
|
21
|
+
opts.on('-p', '--post-to-slack',
|
22
|
+
'Post a summary of changes to Slack (default: false)') do |p|
|
23
|
+
options[:post_to_slack] = p
|
24
|
+
end
|
25
|
+
end.parse!
|
26
|
+
|
27
|
+
puts ChefSync.new(options[:post_to_slack], options[:dryrun]).run
|
data/lib/chef_sync.rb
ADDED
@@ -0,0 +1,82 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
require 'slack/post'
|
3
|
+
|
4
|
+
class ChefSync
|
5
|
+
|
6
|
+
require 'chef_sync/chef_component'
|
7
|
+
require 'chef_sync/chef_component/cookbook'
|
8
|
+
require 'chef_sync/chef_component/data_bag_item'
|
9
|
+
require 'chef_sync/chef_component/environment'
|
10
|
+
require 'chef_sync/chef_component/role'
|
11
|
+
require 'chef_sync/knife'
|
12
|
+
|
13
|
+
RESOURCE_TYPES = [Role, Environment, DataBagItem, Cookbook]
|
14
|
+
|
15
|
+
DRYRUN_MESSAGE = "This was a dry run. Nothing has been updated on the chef server. "
|
16
|
+
DEFAULT_LOG_MESSAGE = "There were no changes."
|
17
|
+
|
18
|
+
def initialize(slack=false,dryrun=true)
|
19
|
+
@slack = slack
|
20
|
+
@dryrun = dryrun
|
21
|
+
@summary = ""
|
22
|
+
@log = []
|
23
|
+
end
|
24
|
+
|
25
|
+
def run
|
26
|
+
@summary = DRYRUN_MESSAGE.dup if @dryrun
|
27
|
+
|
28
|
+
RESOURCE_TYPES.each do |resource|
|
29
|
+
responses = resource.changes(@dryrun)
|
30
|
+
@summary << "#{responses.count}/#{resource.total_resources} #{resource.resource_type}s have changed. "
|
31
|
+
@log += responses
|
32
|
+
end
|
33
|
+
|
34
|
+
@log << DEFAULT_LOG_MESSAGE if @log.empty?
|
35
|
+
|
36
|
+
self.post_to_slack if @slack
|
37
|
+
return @summary, @log
|
38
|
+
end
|
39
|
+
|
40
|
+
def post_to_slack
|
41
|
+
opts = { webhook_url: ENV['CHEFSYNC_SLACK_WEBHOOK_URL'] }
|
42
|
+
opts[:username] = ENV['CHEFSYNC_SLACK_USERNAME'] if ENV['CHEFSYNC_SLACK_USERNAME']
|
43
|
+
opts[:channel] = ENV['CHEFSYNC_SLACK_CHANNEL'] if ENV['CHEFSYNC_SLACK_CHANNEL']
|
44
|
+
|
45
|
+
::Slack::Post.configure( opts )
|
46
|
+
begin
|
47
|
+
::Slack::Post.post_with_attachments(self.pretext, self.slack_attachment)
|
48
|
+
#Assuming that a RuntimeError is due to improperly configured Slack::Post.
|
49
|
+
rescue RuntimeError => e
|
50
|
+
puts "Couldn't post to Slack: #{e}"
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def slack_attachment
|
55
|
+
[
|
56
|
+
{
|
57
|
+
fallback: @summary,
|
58
|
+
fields: [
|
59
|
+
{
|
60
|
+
title: 'Summary',
|
61
|
+
value: @summary,
|
62
|
+
short: false
|
63
|
+
},
|
64
|
+
{
|
65
|
+
title: 'Changes',
|
66
|
+
value: @log.join("\n"),
|
67
|
+
short: false
|
68
|
+
}
|
69
|
+
]
|
70
|
+
}
|
71
|
+
]
|
72
|
+
end
|
73
|
+
|
74
|
+
def pretext
|
75
|
+
if ENV['CHEFSYNC_CI_BUILD_URL'].nil? or ENV['CHEFSYNC_COMMIT_URL'].nil?
|
76
|
+
return "chef-sync run triggered."
|
77
|
+
else
|
78
|
+
return "<#{ENV['CHEFSYNC_CI_BUILD_URL']}|CI build> triggered by <#{ENV['CHEFSYNC_COMMIT_URL']}|commit>."
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
end
|
@@ -0,0 +1,117 @@
|
|
1
|
+
require 'tqdm'
|
2
|
+
|
3
|
+
class ChefSync
|
4
|
+
class ChefComponent
|
5
|
+
|
6
|
+
CHANGE_LOG_SUMMARIES = {
|
7
|
+
:create => " was created.",
|
8
|
+
:update => " was updated."
|
9
|
+
}
|
10
|
+
|
11
|
+
ACTIONABLE_CHANGES = [:create, :update]
|
12
|
+
|
13
|
+
FILE_EXTENSION = ".json"
|
14
|
+
|
15
|
+
class << self; attr_reader :resource_type end
|
16
|
+
class << self; attr_accessor :total_resources end
|
17
|
+
|
18
|
+
attr_reader :name
|
19
|
+
attr_reader :change
|
20
|
+
|
21
|
+
def initialize(name:, local_knife:, remote_knife:, dryrun:)
|
22
|
+
@name = name
|
23
|
+
@name_with_extension = name + FILE_EXTENSION
|
24
|
+
|
25
|
+
@local_knife = local_knife
|
26
|
+
@remote_knife = remote_knife
|
27
|
+
@dryrun = dryrun
|
28
|
+
|
29
|
+
@change = :none
|
30
|
+
end
|
31
|
+
|
32
|
+
def self.each(dryrun)
|
33
|
+
return enum_for(:each, dryrun) unless block_given?
|
34
|
+
|
35
|
+
local_knife = self.make_local_knife
|
36
|
+
remote_knife = self.make_remote_knife
|
37
|
+
|
38
|
+
local_resources = self.get_local_resources(local_knife, remote_knife)
|
39
|
+
self.total_resources = local_resources.count
|
40
|
+
|
41
|
+
default_args = {local_knife: local_knife, remote_knife: remote_knife, dryrun: dryrun}
|
42
|
+
local_resources.tqdm(leave: true, desc: "Checking #{self.resource_type}s").each do |args|
|
43
|
+
resource = self.new(args.merge(default_args))
|
44
|
+
resource.sync
|
45
|
+
yield resource
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def self.changes(dryrun)
|
50
|
+
return self.each(dryrun).select(&:changed?).flat_map(&:summarize_changes)
|
51
|
+
end
|
52
|
+
|
53
|
+
def self.get_local_resources(local_knife, remote_knife)
|
54
|
+
local_resources = local_knife.list
|
55
|
+
return local_resources.map {|resource_name| {name: resource_name}}
|
56
|
+
end
|
57
|
+
|
58
|
+
def self.make_local_knife
|
59
|
+
return ChefSync::Knife.new(self.resource_type, :local)
|
60
|
+
end
|
61
|
+
|
62
|
+
def self.make_remote_knife
|
63
|
+
return ChefSync::Knife.new(self.resource_type, :remote)
|
64
|
+
end
|
65
|
+
|
66
|
+
def changed?
|
67
|
+
return @change != :none
|
68
|
+
end
|
69
|
+
|
70
|
+
def actionable_change?
|
71
|
+
return ACTIONABLE_CHANGES.include?(@change)
|
72
|
+
end
|
73
|
+
|
74
|
+
def summarize_changes
|
75
|
+
return self.resource_path + CHANGE_LOG_SUMMARIES[@change]
|
76
|
+
end
|
77
|
+
|
78
|
+
def resource_path
|
79
|
+
return "#{self.class.resource_type}s/#{@name}"
|
80
|
+
end
|
81
|
+
|
82
|
+
def get_local_resource
|
83
|
+
return @local_knife.show(@name)
|
84
|
+
end
|
85
|
+
|
86
|
+
def get_remote_resource
|
87
|
+
return @remote_knife.show(@name)
|
88
|
+
end
|
89
|
+
|
90
|
+
def upload_resource
|
91
|
+
return @remote_knife.upload(@name_with_extension)
|
92
|
+
end
|
93
|
+
|
94
|
+
def compare_local_and_remote_versions
|
95
|
+
local_resource = self.get_local_resource
|
96
|
+
remote_resource = self.get_remote_resource
|
97
|
+
|
98
|
+
case
|
99
|
+
when remote_resource.empty?
|
100
|
+
@change = :create
|
101
|
+
when local_resource != remote_resource
|
102
|
+
@change = :update
|
103
|
+
end
|
104
|
+
|
105
|
+
return @change
|
106
|
+
end
|
107
|
+
|
108
|
+
def sync
|
109
|
+
action = self.compare_local_and_remote_versions
|
110
|
+
if !@dryrun and self.actionable_change?
|
111
|
+
self.upload_resource
|
112
|
+
end
|
113
|
+
return action
|
114
|
+
end
|
115
|
+
|
116
|
+
end
|
117
|
+
end
|
@@ -0,0 +1,99 @@
|
|
1
|
+
require 'chef'
|
2
|
+
require 'ridley'
|
3
|
+
require 'mixlib/versioning'
|
4
|
+
|
5
|
+
Ridley::Logging.logger.level = Logger.const_get('ERROR')
|
6
|
+
|
7
|
+
class ChefSync::Cookbook < ChefSync::ChefComponent
|
8
|
+
|
9
|
+
CHANGE_LOG_SUMMARIES = {
|
10
|
+
:create => " was created.",
|
11
|
+
:update => " was updated.",
|
12
|
+
:version_regressed => " is newer than the local version.",
|
13
|
+
:version_changed => " has changed without a version number increase."
|
14
|
+
}
|
15
|
+
|
16
|
+
FILE_CHANGE_LOG_SUMMARIES = {
|
17
|
+
:file_changed => " has changed.",
|
18
|
+
:file_missing => " does not exist locally."
|
19
|
+
}
|
20
|
+
|
21
|
+
@resource_type = 'cookbook'
|
22
|
+
|
23
|
+
attr_reader :file_change_log
|
24
|
+
|
25
|
+
def initialize(local_version_number:, remote_version_number:, **opts)
|
26
|
+
@local_version_number = Mixlib::Versioning.parse(local_version_number)
|
27
|
+
@remote_version_number = Mixlib::Versioning.parse(remote_version_number)
|
28
|
+
@file_change_log = {}
|
29
|
+
|
30
|
+
super(opts)
|
31
|
+
end
|
32
|
+
|
33
|
+
def self.get_local_resources(local_knife, remote_knife)
|
34
|
+
local_cookbooks = local_knife.list
|
35
|
+
remote_cookbooks = remote_knife.list
|
36
|
+
|
37
|
+
return local_cookbooks.map do |cb, local_ver|
|
38
|
+
{name: cb, local_version_number: local_ver, remote_version_number: remote_cookbooks[cb]}
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def version_changed?
|
43
|
+
return @change == :version_changed
|
44
|
+
end
|
45
|
+
|
46
|
+
def summarize_changes
|
47
|
+
self.actionable_change? ? prefix = "" : prefix = "WARNING: "
|
48
|
+
summary = [prefix + self.resource_path + CHANGE_LOG_SUMMARIES[@change]]
|
49
|
+
if self.version_changed?
|
50
|
+
summary << @file_change_log.map {|file, file_action| file + FILE_CHANGE_LOG_SUMMARIES[file_action]}
|
51
|
+
end
|
52
|
+
return summary
|
53
|
+
end
|
54
|
+
|
55
|
+
def get_remote_resource
|
56
|
+
ridley = Ridley.from_chef_config
|
57
|
+
remote_cookbook = ridley.cookbook.find(@name, @remote_version_number)
|
58
|
+
remote_cookbook_files = Chef::CookbookVersion::COOKBOOK_SEGMENTS.collect { |d| remote_cookbook.method(d).call }.flatten
|
59
|
+
return remote_cookbook_files
|
60
|
+
end
|
61
|
+
|
62
|
+
def upload_resource
|
63
|
+
return @remote_knife.upload(@name, '--freeze')
|
64
|
+
end
|
65
|
+
|
66
|
+
def compare_cookbook_files
|
67
|
+
remote_cookbook_files = self.get_remote_resource
|
68
|
+
|
69
|
+
remote_cookbook_files.each do |remote_file|
|
70
|
+
local_file_path = "#{self.resource_path}/#{remote_file['path']}"
|
71
|
+
begin
|
72
|
+
local_file_checksum = Chef::CookbookVersion.checksum_cookbook_file(File.open(local_file_path))
|
73
|
+
@file_change_log[local_file_path] = :file_changed unless local_file_checksum == remote_file['checksum']
|
74
|
+
rescue Errno::ENOENT => e
|
75
|
+
@file_change_log[local_file_path] = :file_missing
|
76
|
+
end
|
77
|
+
end
|
78
|
+
return @file_change_log
|
79
|
+
end
|
80
|
+
|
81
|
+
def compare_local_and_remote_versions
|
82
|
+
local_ver = @local_version_number
|
83
|
+
remote_ver = @remote_version_number
|
84
|
+
|
85
|
+
case
|
86
|
+
when !@remote_version_number
|
87
|
+
@change = :create
|
88
|
+
when @local_version_number < @remote_version_number
|
89
|
+
@change = :version_regressed
|
90
|
+
when @local_version_number == @remote_version_number
|
91
|
+
@change = :version_changed unless self.compare_cookbook_files.empty?
|
92
|
+
when @local_version_number > @remote_version_number
|
93
|
+
@change = :update
|
94
|
+
end
|
95
|
+
|
96
|
+
return @change
|
97
|
+
end
|
98
|
+
|
99
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
class ChefSync::DataBagItem < ChefSync::ChefComponent
|
2
|
+
|
3
|
+
@resource_type = 'data_bag'
|
4
|
+
|
5
|
+
def initialize(data_bag:, **opts)
|
6
|
+
@data_bag = data_bag
|
7
|
+
|
8
|
+
super(opts)
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.get_local_resources(local_knife, remote_knife)
|
12
|
+
local_data_bag_list = local_knife.list
|
13
|
+
|
14
|
+
return local_data_bag_list.flat_map do |dbag|
|
15
|
+
local_knife.show(dbag).map {|item| {name: item, data_bag: dbag}}
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def resource_path
|
20
|
+
return "#{self.class.resource_type}s/#{@data_bag}/#{@name}"
|
21
|
+
end
|
22
|
+
|
23
|
+
def get_local_resource
|
24
|
+
return @local_knife.show(@data_bag, @name)
|
25
|
+
end
|
26
|
+
|
27
|
+
def get_remote_resource
|
28
|
+
return @remote_knife.show(@data_bag, @name)
|
29
|
+
end
|
30
|
+
|
31
|
+
def upload_resource
|
32
|
+
return @remote_knife.upload(@data_bag, @name_with_extension)
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
@@ -0,0 +1,113 @@
|
|
1
|
+
require 'json'
|
2
|
+
require 'knife/api'
|
3
|
+
|
4
|
+
class ChefSync
|
5
|
+
class Knife
|
6
|
+
|
7
|
+
#Need to extend Chef::Knife::API in this class because knife_capture is top-level.
|
8
|
+
extend Chef::Knife::API
|
9
|
+
|
10
|
+
attr_reader :chef_component
|
11
|
+
attr_reader :list_command
|
12
|
+
attr_reader :show_command
|
13
|
+
attr_reader :upload_command
|
14
|
+
|
15
|
+
def initialize(chef_component, mode)
|
16
|
+
@chef_component = chef_component
|
17
|
+
@list_command = "#{chef_component}_list".to_sym
|
18
|
+
@show_command = "#{chef_component}_show".to_sym
|
19
|
+
|
20
|
+
if chef_component == 'cookbook'
|
21
|
+
@upload_command = "#{chef_component}_upload".to_sym
|
22
|
+
else
|
23
|
+
@upload_command = "#{chef_component}_from_file".to_sym
|
24
|
+
end
|
25
|
+
|
26
|
+
@mode = mode
|
27
|
+
end
|
28
|
+
|
29
|
+
# This is the hackiest hack ever.
|
30
|
+
# Chef::Knife keeps a persistent option state -- if you add
|
31
|
+
# an option like `--local-mode` once, it will be used on
|
32
|
+
# _every_ future call to Chef::Knife. I would _love_ to
|
33
|
+
# figure out how to change this behavior (lost many hours trying),
|
34
|
+
# but until then, we fork a new process for `knife_capture` so
|
35
|
+
# that we do not taint our global state.
|
36
|
+
# This has to stay a class method for the same reason.
|
37
|
+
def self.fork_knife_capture(command, args)
|
38
|
+
reader, writer = IO.pipe
|
39
|
+
|
40
|
+
pid = fork do
|
41
|
+
reader.close
|
42
|
+
output, stderr, status = knife_capture(command, args)
|
43
|
+
Marshal.dump([output, stderr, status], writer)
|
44
|
+
end
|
45
|
+
|
46
|
+
writer.close
|
47
|
+
command_output = reader.read
|
48
|
+
Process.wait(pid)
|
49
|
+
return Marshal.load(command_output)
|
50
|
+
end
|
51
|
+
|
52
|
+
# This instance method exists so we can mock out Knife's output for
|
53
|
+
# individual instances when testing.
|
54
|
+
def fork_knife_capture(command, args)
|
55
|
+
return self.class.fork_knife_capture(command, args)
|
56
|
+
end
|
57
|
+
|
58
|
+
def local?
|
59
|
+
return @mode == :local
|
60
|
+
end
|
61
|
+
|
62
|
+
def parse_output(command, args, output)
|
63
|
+
stdout, stderr, status = output
|
64
|
+
|
65
|
+
begin
|
66
|
+
return JSON.parse(stdout)
|
67
|
+
#Assuming here that a parser error means no data was returned and there was a knife error.
|
68
|
+
rescue JSON::ParserError => e
|
69
|
+
puts "Received STDERR #{stderr} when trying to run knife_capture(#{command}, #{args})."
|
70
|
+
return stdout
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
#Helper method to parse knife cookbook list into cookbooks.
|
75
|
+
def format_cookbook_list_output(knife_output)
|
76
|
+
cookbooks = {}
|
77
|
+
knife_output.each do |c|
|
78
|
+
cb, ver = c.gsub(/\s+/m, ' ').strip.split(" ")
|
79
|
+
cookbooks[cb] = ver
|
80
|
+
end
|
81
|
+
return cookbooks
|
82
|
+
end
|
83
|
+
|
84
|
+
def capture_output(command, args)
|
85
|
+
args << '-fj'
|
86
|
+
args << '-z' if self.local?
|
87
|
+
knife_output = self.fork_knife_capture(command, args)
|
88
|
+
return self.parse_output(command, args, knife_output)
|
89
|
+
end
|
90
|
+
|
91
|
+
def list(*args)
|
92
|
+
parsed_output = self.capture_output(self.list_command, args)
|
93
|
+
if self.chef_component == 'cookbook'
|
94
|
+
parsed_output = self.format_cookbook_list_output(parsed_output)
|
95
|
+
end
|
96
|
+
return parsed_output
|
97
|
+
end
|
98
|
+
|
99
|
+
def show(*args)
|
100
|
+
return self.capture_output(self.show_command, args)
|
101
|
+
end
|
102
|
+
|
103
|
+
def upload(*args)
|
104
|
+
knife_output = self.fork_knife_capture(self.upload_command, args)
|
105
|
+
stdout, stderr, status = knife_output
|
106
|
+
unless status == 0
|
107
|
+
puts "Received STDERR #{stderr} when trying to run knife_capture(#{self.upload_command}, #{args})."
|
108
|
+
return false
|
109
|
+
end
|
110
|
+
return true
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
@@ -0,0 +1,117 @@
|
|
1
|
+
require_relative '../../spec_helper'
|
2
|
+
|
3
|
+
describe 'ChefSync::Cookbook' do
|
4
|
+
|
5
|
+
let(:local_knife) do
|
6
|
+
ChefSync::KnifeMock.new(ChefSync::Cookbook.resource_type, :local)
|
7
|
+
end
|
8
|
+
|
9
|
+
let(:remote_knife) do
|
10
|
+
ChefSync::KnifeMock.new(ChefSync::Cookbook.resource_type, :remote)
|
11
|
+
end
|
12
|
+
|
13
|
+
let(:dryrun_args) do
|
14
|
+
{
|
15
|
+
name: 'boyardee',
|
16
|
+
local_knife: local_knife,
|
17
|
+
remote_knife: remote_knife,
|
18
|
+
dryrun: true
|
19
|
+
}
|
20
|
+
end
|
21
|
+
|
22
|
+
let(:no_dryrun_args) {dryrun_args.merge(dryrun: false)}
|
23
|
+
|
24
|
+
context 'when the local and remote are the same version number' do
|
25
|
+
|
26
|
+
let(:args) do
|
27
|
+
dryrun_args.merge({local_version_number: '0.1.0', remote_version_number: '0.1.0'})
|
28
|
+
end
|
29
|
+
|
30
|
+
it 'has no required actions when all files are the same' do
|
31
|
+
cb = ChefSync::Cookbook.new(args)
|
32
|
+
expect(cb).to receive(:upload_resource).never
|
33
|
+
expect(cb).to receive(:compare_cookbook_files).and_return([])
|
34
|
+
|
35
|
+
action = cb.sync
|
36
|
+
expect(action).to be_a(Symbol)
|
37
|
+
expect(action).to eq(:none)
|
38
|
+
end
|
39
|
+
|
40
|
+
it 'needs to be updated when a file is different' do
|
41
|
+
cb = ChefSync::Cookbook.new(args)
|
42
|
+
expect(cb).to receive(:upload_resource).never
|
43
|
+
expect(cb).to receive(:compare_cookbook_files).and_return([{'spaghetti' => :file_changed}])
|
44
|
+
|
45
|
+
action = cb.sync
|
46
|
+
expect(action).to be_a(Symbol)
|
47
|
+
expect(action).to eq(:version_changed)
|
48
|
+
end
|
49
|
+
|
50
|
+
it 'needs to be updated when a file does not exist locally' do
|
51
|
+
cb = ChefSync::Cookbook.new(args)
|
52
|
+
expect(cb).to receive(:upload_resource).never
|
53
|
+
expect(cb).to receive(:compare_cookbook_files).and_return([{'meatballs' => :file_missing}])
|
54
|
+
|
55
|
+
action = cb.sync
|
56
|
+
expect(action).to be_a(Symbol)
|
57
|
+
expect(action).to eq(:version_changed)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
context 'when the local version is newer' do
|
62
|
+
|
63
|
+
let(:version_args) { {local_version_number: '0.1.10', remote_version_number: '0.1.9'} }
|
64
|
+
|
65
|
+
it 'needs to be updated' do
|
66
|
+
cb = ChefSync::Cookbook.new(dryrun_args.merge(version_args))
|
67
|
+
expect(cb).to receive(:upload_resource).never
|
68
|
+
|
69
|
+
action = cb.sync
|
70
|
+
expect(action).to be_a(Symbol)
|
71
|
+
expect(action).to eq(:update)
|
72
|
+
end
|
73
|
+
|
74
|
+
it 'uploads the new cookbook version when not in dryrun mode' do
|
75
|
+
remote_knife.set_success("")
|
76
|
+
|
77
|
+
cb = ChefSync::Cookbook.new(no_dryrun_args.merge(version_args))
|
78
|
+
expect(cb).to receive(:upload_resource)
|
79
|
+
cb.sync
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
context 'when the local version is older' do
|
84
|
+
it 'returns an error message' do
|
85
|
+
version_args = {local_version_number: '0.1.9', remote_version_number: '0.1.10'}
|
86
|
+
cb = ChefSync::Cookbook.new(dryrun_args.merge(version_args))
|
87
|
+
expect(cb).to receive(:upload_resource).never
|
88
|
+
|
89
|
+
action = cb.sync
|
90
|
+
expect(action).to be_a(Symbol)
|
91
|
+
expect(action).to eq(:version_regressed)
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
context 'when the remote does not exist' do
|
96
|
+
|
97
|
+
let(:version_args) { {local_version_number: '0.1.0', remote_version_number: nil} }
|
98
|
+
|
99
|
+
it 'needs to be created' do
|
100
|
+
cb = ChefSync::Cookbook.new(dryrun_args.merge(version_args))
|
101
|
+
expect(cb).to receive(:upload_resource).never
|
102
|
+
|
103
|
+
action = cb.sync
|
104
|
+
expect(action).to be_a(Symbol)
|
105
|
+
expect(action).to eq(:create)
|
106
|
+
end
|
107
|
+
|
108
|
+
it 'uploads the new cookbook when not in dryrun mode' do
|
109
|
+
remote_knife.set_success("")
|
110
|
+
|
111
|
+
cb = ChefSync::Cookbook.new(no_dryrun_args.merge(version_args))
|
112
|
+
expect(cb).to receive(:upload_resource)
|
113
|
+
cb.sync
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require_relative '../../spec_helper'
|
2
|
+
require_relative '../chef_component_shared_behaviors'
|
3
|
+
|
4
|
+
describe 'ChefSync::DataBagItem' do
|
5
|
+
|
6
|
+
let(:resource_class) {ChefSync::DataBagItem}
|
7
|
+
|
8
|
+
let(:local_resource) do
|
9
|
+
{
|
10
|
+
'id': 'fake_dbag',
|
11
|
+
'data': {
|
12
|
+
'stuff': 'fake data'
|
13
|
+
}
|
14
|
+
}
|
15
|
+
end
|
16
|
+
|
17
|
+
let(:remote_resource) do
|
18
|
+
local_resource.merge({'data' => {'stuff' => 'different fake data'}})
|
19
|
+
end
|
20
|
+
|
21
|
+
let(:init_args) { {name: 'fake_data_bag_item', data_bag: 'fake_data_bag'} }
|
22
|
+
|
23
|
+
it_should_behave_like 'a chef resource'
|
24
|
+
|
25
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
require_relative '../../spec_helper'
|
2
|
+
require_relative '../chef_component_shared_behaviors'
|
3
|
+
|
4
|
+
describe 'ChefSync::Environment' do
|
5
|
+
|
6
|
+
let(:resource_class) {ChefSync::Environment}
|
7
|
+
|
8
|
+
let(:local_resource) do
|
9
|
+
{
|
10
|
+
'name': 'fake_environment',
|
11
|
+
'default_attributes': {},
|
12
|
+
'override_attributes': {},
|
13
|
+
'json_class': 'Chef::Environment',
|
14
|
+
'description': 'Fake chef environment.',
|
15
|
+
'cookbook_versions': {},
|
16
|
+
'chef_type': 'environment'
|
17
|
+
}
|
18
|
+
end
|
19
|
+
|
20
|
+
let(:remote_resource) do
|
21
|
+
local_resource.merge({'description' => 'This is a different fake environment.'})
|
22
|
+
end
|
23
|
+
|
24
|
+
let(:init_args) { {name: 'fake_environment'} }
|
25
|
+
|
26
|
+
it_should_behave_like 'a chef resource'
|
27
|
+
|
28
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
require_relative '../../spec_helper'
|
2
|
+
require_relative '../chef_component_shared_behaviors'
|
3
|
+
|
4
|
+
describe 'ChefSync::Role' do
|
5
|
+
|
6
|
+
let(:resource_class) {ChefSync::Role}
|
7
|
+
|
8
|
+
let(:local_resource) do
|
9
|
+
{
|
10
|
+
'name': 'fake_role',
|
11
|
+
'default_attributes': {},
|
12
|
+
'override_attributes': {},
|
13
|
+
'json_class': 'Chef::Role',
|
14
|
+
'description': 'Fake chef role.',
|
15
|
+
'chef_type': 'role',
|
16
|
+
'run_list': [],
|
17
|
+
'env_run_lists': {}
|
18
|
+
}
|
19
|
+
end
|
20
|
+
|
21
|
+
let(:remote_resource) do
|
22
|
+
local_resource.merge({'description' => 'This is a different fake chef role.'})
|
23
|
+
end
|
24
|
+
|
25
|
+
let(:init_args) { {name: 'fake_role'} }
|
26
|
+
|
27
|
+
it_should_behave_like 'a chef resource'
|
28
|
+
|
29
|
+
end
|
@@ -0,0 +1,86 @@
|
|
1
|
+
require_relative '../spec_helper'
|
2
|
+
|
3
|
+
RSpec.shared_examples 'a chef resource' do
|
4
|
+
|
5
|
+
let(:local_knife) do
|
6
|
+
local_knife = ChefSync::KnifeMock.new(resource_class.resource_type, :local)
|
7
|
+
local_knife.set_success(local_resource)
|
8
|
+
local_knife
|
9
|
+
end
|
10
|
+
|
11
|
+
let(:remote_knife) do
|
12
|
+
ChefSync::KnifeMock.new(resource_class.resource_type, :remote)
|
13
|
+
end
|
14
|
+
|
15
|
+
let(:dryrun_args) do
|
16
|
+
init_args.merge({local_knife: local_knife, remote_knife: remote_knife, dryrun: true})
|
17
|
+
end
|
18
|
+
|
19
|
+
let(:no_dryrun_args) {dryrun_args.merge({dryrun: false})}
|
20
|
+
|
21
|
+
context 'when the local and remote are the same' do
|
22
|
+
|
23
|
+
it 'has no required action' do
|
24
|
+
remote_knife.set_success(local_resource)
|
25
|
+
|
26
|
+
resource = resource_class.new(dryrun_args)
|
27
|
+
expect(resource).to receive(:upload_resource).never
|
28
|
+
|
29
|
+
action = resource.sync
|
30
|
+
expect(action).to be_a(Symbol)
|
31
|
+
expect(action).to eq(:none)
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
35
|
+
|
36
|
+
|
37
|
+
context 'when the local and remote are different' do
|
38
|
+
|
39
|
+
it 'needs to be updated' do
|
40
|
+
remote_knife.set_success(remote_resource)
|
41
|
+
|
42
|
+
resource = resource_class.new(dryrun_args)
|
43
|
+
expect(resource).to receive(:upload_resource).never
|
44
|
+
|
45
|
+
action = resource.sync
|
46
|
+
expect(action).to be_a(Symbol)
|
47
|
+
expect(action).to eq(:update)
|
48
|
+
end
|
49
|
+
|
50
|
+
it 'uploads the changed resource when not in dryrun mode' do
|
51
|
+
remote_knife.set_success(remote_resource)
|
52
|
+
|
53
|
+
resource = resource_class.new(no_dryrun_args)
|
54
|
+
expect(resource).to receive(:upload_resource)
|
55
|
+
resource.sync
|
56
|
+
end
|
57
|
+
|
58
|
+
end
|
59
|
+
|
60
|
+
|
61
|
+
context 'when the remote does not exist' do
|
62
|
+
|
63
|
+
it 'needs to be created' do
|
64
|
+
error = "ERROR: The object you are looking for could not be found"
|
65
|
+
remote_knife.set_error(error, 100)
|
66
|
+
|
67
|
+
resource = resource_class.new(dryrun_args)
|
68
|
+
expect(resource).to receive(:upload_resource).never
|
69
|
+
|
70
|
+
action = resource.sync
|
71
|
+
expect(action).to be_a(Symbol)
|
72
|
+
expect(action).to eq(:create)
|
73
|
+
end
|
74
|
+
|
75
|
+
it 'uploads the new resource when not in dryrun mode' do
|
76
|
+
error = "ERROR: The object you are looking for could not be found"
|
77
|
+
remote_knife.set_error(error, 100)
|
78
|
+
|
79
|
+
resource = resource_class.new(no_dryrun_args)
|
80
|
+
expect(resource).to receive(:upload_resource)
|
81
|
+
resource.sync
|
82
|
+
end
|
83
|
+
|
84
|
+
end
|
85
|
+
|
86
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
require_relative '../spec_helper'
|
2
|
+
|
3
|
+
describe 'ChefSync::ChefComponent' do
|
4
|
+
|
5
|
+
let(:local_knife_list) {['badger', 'mushroom', 'snake']}
|
6
|
+
|
7
|
+
let(:local_knife) do
|
8
|
+
local_knife = ChefSync::KnifeMock.new(ChefSync::ChefComponentMock.resource_type, :local)
|
9
|
+
local_knife.set_success(local_knife_list)
|
10
|
+
local_knife
|
11
|
+
end
|
12
|
+
|
13
|
+
it 'creates an array of formatted summary strings for its resources' do
|
14
|
+
expect(ChefSync::ChefComponentMock).to receive(:make_local_knife).and_return(local_knife)
|
15
|
+
expect(ChefSync::ChefComponentMock).to receive(:make_remote_knife).and_return(local_knife)
|
16
|
+
|
17
|
+
result = ChefSync::ChefComponentMock.changes(true)
|
18
|
+
|
19
|
+
expect(result.count).to eq(3)
|
20
|
+
expect(result.first).to include(local_knife_list.first)
|
21
|
+
expect(result.last).to include(local_knife_list.last)
|
22
|
+
expect(result).to all(include('was updated.'))
|
23
|
+
end
|
24
|
+
|
25
|
+
it 'creates an instance for each local resource and syncs them' do
|
26
|
+
expect(ChefSync::ChefComponentMock).to receive(:make_local_knife).and_return(local_knife)
|
27
|
+
expect(ChefSync::ChefComponentMock).to receive(:make_remote_knife).and_return(local_knife)
|
28
|
+
|
29
|
+
enum = ChefSync::ChefComponentMock.each(true)
|
30
|
+
resources = [enum.next, enum.next, enum.next]
|
31
|
+
|
32
|
+
expect(resources.count).to eq(3)
|
33
|
+
expect(resources.map(&:name)).to eq(local_knife_list)
|
34
|
+
expect(resources.map(&:sync_called?)).to all(be_truthy)
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe 'ChefSync' do
|
4
|
+
|
5
|
+
before(:all) do
|
6
|
+
ChefSync::RESOURCE_TYPES.each {|r| r.total_resources = 3}
|
7
|
+
end
|
8
|
+
|
9
|
+
it 'has no log when there are are no actionable changes or warnings' do
|
10
|
+
ChefSync::RESOURCE_TYPES.each {|r| allow(r).to receive(:changes).and_return([])}
|
11
|
+
|
12
|
+
summ = ChefSync::DRYRUN_MESSAGE.dup
|
13
|
+
ChefSync::RESOURCE_TYPES.each do |r|
|
14
|
+
summ << "#{r.changes.count}/#{r.total_resources} #{r.resource_type}s have changed. "
|
15
|
+
end
|
16
|
+
|
17
|
+
expect(ChefSync.new.run).to eq([summ, [ChefSync::DEFAULT_LOG_MESSAGE]])
|
18
|
+
end
|
19
|
+
|
20
|
+
it 'has log entries when there is a change' do
|
21
|
+
non_cookbooks = [ChefSync::Role, ChefSync::Environment, ChefSync::DataBagItem]
|
22
|
+
non_cookbooks.each {|r| allow(r).to receive(:changes).and_return([])}
|
23
|
+
cookbook_warning = 'cookbooks/fake_cookbook is newer than the local version.'
|
24
|
+
allow(ChefSync::Cookbook).to receive(:changes).and_return([cookbook_warning])
|
25
|
+
|
26
|
+
summ = ChefSync::DRYRUN_MESSAGE.dup
|
27
|
+
ChefSync::RESOURCE_TYPES.each do |r|
|
28
|
+
summ << "#{r.changes.count}/#{r.total_resources} #{r.resource_type}s have changed. "
|
29
|
+
end
|
30
|
+
|
31
|
+
expect(ChefSync.new.run).to eq([summ, [cookbook_warning]])
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,53 @@
|
|
1
|
+
require 'chef_sync'
|
2
|
+
|
3
|
+
require 'json'
|
4
|
+
require 'knife/api'
|
5
|
+
require 'rspec'
|
6
|
+
|
7
|
+
class ChefSync
|
8
|
+
class KnifeMock < Knife
|
9
|
+
|
10
|
+
def set_success(hash)
|
11
|
+
@result = [hash.to_json, '', 0]
|
12
|
+
end
|
13
|
+
|
14
|
+
def set_error(err, status)
|
15
|
+
@result = ['', err, status]
|
16
|
+
end
|
17
|
+
|
18
|
+
def fork_knife_capture(command, args)
|
19
|
+
return @result
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
23
|
+
|
24
|
+
|
25
|
+
class ChefComponentMock < ChefComponent
|
26
|
+
|
27
|
+
@resource_type = 'fake_resource'
|
28
|
+
|
29
|
+
attr_accessor :sync_called
|
30
|
+
|
31
|
+
def initialize( *args )
|
32
|
+
super
|
33
|
+
@sync_called = false
|
34
|
+
@change = :update
|
35
|
+
end
|
36
|
+
|
37
|
+
def sync
|
38
|
+
@sync_called = true
|
39
|
+
end
|
40
|
+
|
41
|
+
def sync_called?
|
42
|
+
return @sync_called
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
48
|
+
|
49
|
+
RSpec.configure do |config|
|
50
|
+
config.run_all_when_everything_filtered = true
|
51
|
+
config.filter_run :focus
|
52
|
+
end
|
53
|
+
|
metadata
ADDED
@@ -0,0 +1,185 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: chef_synchronize
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.7
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Cozy Services Ltd.
|
8
|
+
- Rachel King
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2016-03-18 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: bundler
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
requirements:
|
18
|
+
- - "~>"
|
19
|
+
- !ruby/object:Gem::Version
|
20
|
+
version: '1.6'
|
21
|
+
type: :development
|
22
|
+
prerelease: false
|
23
|
+
version_requirements: !ruby/object:Gem::Requirement
|
24
|
+
requirements:
|
25
|
+
- - "~>"
|
26
|
+
- !ruby/object:Gem::Version
|
27
|
+
version: '1.6'
|
28
|
+
- !ruby/object:Gem::Dependency
|
29
|
+
name: rake
|
30
|
+
requirement: !ruby/object:Gem::Requirement
|
31
|
+
requirements:
|
32
|
+
- - "~>"
|
33
|
+
- !ruby/object:Gem::Version
|
34
|
+
version: '10.0'
|
35
|
+
type: :development
|
36
|
+
prerelease: false
|
37
|
+
version_requirements: !ruby/object:Gem::Requirement
|
38
|
+
requirements:
|
39
|
+
- - "~>"
|
40
|
+
- !ruby/object:Gem::Version
|
41
|
+
version: '10.0'
|
42
|
+
- !ruby/object:Gem::Dependency
|
43
|
+
name: rspec
|
44
|
+
requirement: !ruby/object:Gem::Requirement
|
45
|
+
requirements:
|
46
|
+
- - "~>"
|
47
|
+
- !ruby/object:Gem::Version
|
48
|
+
version: '3.4'
|
49
|
+
type: :development
|
50
|
+
prerelease: false
|
51
|
+
version_requirements: !ruby/object:Gem::Requirement
|
52
|
+
requirements:
|
53
|
+
- - "~>"
|
54
|
+
- !ruby/object:Gem::Version
|
55
|
+
version: '3.4'
|
56
|
+
- !ruby/object:Gem::Dependency
|
57
|
+
name: ridley
|
58
|
+
requirement: !ruby/object:Gem::Requirement
|
59
|
+
requirements:
|
60
|
+
- - "~>"
|
61
|
+
- !ruby/object:Gem::Version
|
62
|
+
version: '4.4'
|
63
|
+
type: :runtime
|
64
|
+
prerelease: false
|
65
|
+
version_requirements: !ruby/object:Gem::Requirement
|
66
|
+
requirements:
|
67
|
+
- - "~>"
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: '4.4'
|
70
|
+
- !ruby/object:Gem::Dependency
|
71
|
+
name: knife-api
|
72
|
+
requirement: !ruby/object:Gem::Requirement
|
73
|
+
requirements:
|
74
|
+
- - "~>"
|
75
|
+
- !ruby/object:Gem::Version
|
76
|
+
version: '0.1'
|
77
|
+
type: :runtime
|
78
|
+
prerelease: false
|
79
|
+
version_requirements: !ruby/object:Gem::Requirement
|
80
|
+
requirements:
|
81
|
+
- - "~>"
|
82
|
+
- !ruby/object:Gem::Version
|
83
|
+
version: '0.1'
|
84
|
+
- !ruby/object:Gem::Dependency
|
85
|
+
name: slack-post
|
86
|
+
requirement: !ruby/object:Gem::Requirement
|
87
|
+
requirements:
|
88
|
+
- - "~>"
|
89
|
+
- !ruby/object:Gem::Version
|
90
|
+
version: '0.3'
|
91
|
+
type: :runtime
|
92
|
+
prerelease: false
|
93
|
+
version_requirements: !ruby/object:Gem::Requirement
|
94
|
+
requirements:
|
95
|
+
- - "~>"
|
96
|
+
- !ruby/object:Gem::Version
|
97
|
+
version: '0.3'
|
98
|
+
- !ruby/object:Gem::Dependency
|
99
|
+
name: tqdm
|
100
|
+
requirement: !ruby/object:Gem::Requirement
|
101
|
+
requirements:
|
102
|
+
- - "~>"
|
103
|
+
- !ruby/object:Gem::Version
|
104
|
+
version: '0.3'
|
105
|
+
type: :runtime
|
106
|
+
prerelease: false
|
107
|
+
version_requirements: !ruby/object:Gem::Requirement
|
108
|
+
requirements:
|
109
|
+
- - "~>"
|
110
|
+
- !ruby/object:Gem::Version
|
111
|
+
version: '0.3'
|
112
|
+
- !ruby/object:Gem::Dependency
|
113
|
+
name: mixlib-versioning
|
114
|
+
requirement: !ruby/object:Gem::Requirement
|
115
|
+
requirements:
|
116
|
+
- - "~>"
|
117
|
+
- !ruby/object:Gem::Version
|
118
|
+
version: '1.1'
|
119
|
+
type: :runtime
|
120
|
+
prerelease: false
|
121
|
+
version_requirements: !ruby/object:Gem::Requirement
|
122
|
+
requirements:
|
123
|
+
- - "~>"
|
124
|
+
- !ruby/object:Gem::Version
|
125
|
+
version: '1.1'
|
126
|
+
description: Sync a monolithic chef repo to a chef server.
|
127
|
+
email:
|
128
|
+
- opensource@cozy.co
|
129
|
+
executables:
|
130
|
+
- chef_sync
|
131
|
+
extensions: []
|
132
|
+
extra_rdoc_files: []
|
133
|
+
files:
|
134
|
+
- LICENSE.txt
|
135
|
+
- README.md
|
136
|
+
- bin/chef_sync
|
137
|
+
- lib/chef_sync.rb
|
138
|
+
- lib/chef_sync/chef_component.rb
|
139
|
+
- lib/chef_sync/chef_component/cookbook.rb
|
140
|
+
- lib/chef_sync/chef_component/data_bag_item.rb
|
141
|
+
- lib/chef_sync/chef_component/environment.rb
|
142
|
+
- lib/chef_sync/chef_component/role.rb
|
143
|
+
- lib/chef_sync/knife.rb
|
144
|
+
- lib/chef_sync/version.rb
|
145
|
+
- spec/chef_sync/chef_component/cookbook_spec.rb
|
146
|
+
- spec/chef_sync/chef_component/data_bag_item_spec.rb
|
147
|
+
- spec/chef_sync/chef_component/environment_spec.rb
|
148
|
+
- spec/chef_sync/chef_component/role_spec.rb
|
149
|
+
- spec/chef_sync/chef_component_shared_behaviors.rb
|
150
|
+
- spec/chef_sync/chef_component_spec.rb
|
151
|
+
- spec/chef_sync_spec.rb
|
152
|
+
- spec/spec_helper.rb
|
153
|
+
homepage: https://github.com/CozyCo/chef_synchronize
|
154
|
+
licenses:
|
155
|
+
- MIT
|
156
|
+
metadata: {}
|
157
|
+
post_install_message:
|
158
|
+
rdoc_options: []
|
159
|
+
require_paths:
|
160
|
+
- lib
|
161
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
162
|
+
requirements:
|
163
|
+
- - ">="
|
164
|
+
- !ruby/object:Gem::Version
|
165
|
+
version: '0'
|
166
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
167
|
+
requirements:
|
168
|
+
- - ">="
|
169
|
+
- !ruby/object:Gem::Version
|
170
|
+
version: '0'
|
171
|
+
requirements: []
|
172
|
+
rubyforge_project:
|
173
|
+
rubygems_version: 2.4.5
|
174
|
+
signing_key:
|
175
|
+
specification_version: 4
|
176
|
+
summary: Sync a monolithic chef repo to a chef server.
|
177
|
+
test_files:
|
178
|
+
- spec/chef_sync/chef_component/cookbook_spec.rb
|
179
|
+
- spec/chef_sync/chef_component/data_bag_item_spec.rb
|
180
|
+
- spec/chef_sync/chef_component/environment_spec.rb
|
181
|
+
- spec/chef_sync/chef_component/role_spec.rb
|
182
|
+
- spec/chef_sync/chef_component_shared_behaviors.rb
|
183
|
+
- spec/chef_sync/chef_component_spec.rb
|
184
|
+
- spec/chef_sync_spec.rb
|
185
|
+
- spec/spec_helper.rb
|