ridley 0.7.0.rc4 → 0.7.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.
@@ -0,0 +1,162 @@
1
+ module Ridley::Chef
2
+ class Cookbook
3
+ # @author Jamie Winsor <jamie@vialstudios.com>
4
+ #
5
+ # Encapsulates the process of validating the ruby syntax of files in Chef
6
+ # cookbooks.
7
+ #
8
+ # Borrowed and modified from: {https://github.com/opscode/chef/blob/11.4.0/lib/chef/cookbook/syntax_check.rb}
9
+ #
10
+ # Copyright:: Copyright (c) 2010 Opscode, Inc.
11
+ #
12
+ # Licensed under the Apache License, Version 2.0 (the "License");
13
+ # you may not use this file except in compliance with the License.
14
+ # You may obtain a copy of the License at
15
+ #
16
+ # http://www.apache.org/licenses/LICENSE-2.0
17
+ #
18
+ # Unless required by applicable law or agreed to in writing, software
19
+ # distributed under the License is distributed on an "AS IS" BASIS,
20
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
21
+ # See the License for the specific language governing permissions and
22
+ # limitations under the License.
23
+ class SyntaxCheck
24
+ # Implements set behavior with disk-based persistence. Objects in the set
25
+ # are expected to be strings containing only characters that are valid in
26
+ # filenames.
27
+ #
28
+ # This class is used to track which files have been syntax checked so
29
+ # that known good files are not rechecked.
30
+ class PersistentSet
31
+ attr_reader :cache_path
32
+
33
+ # Create a new PersistentSet. Values in the set are persisted by
34
+ # creating a file in the +cache_path+ directory. If not given, the
35
+ # value of Chef::Config[:syntax_check_cache_path] is used; if that
36
+ # value is not configured, the value of
37
+ # Chef::Config[:cache_options][:path] is used.
38
+ #--
39
+ # history: prior to Chef 11, the cache implementation was based on
40
+ # moneta and configured via cache_options[:path]. Knife configs
41
+ # generated with Chef 11 will have `syntax_check_cache_path`, but older
42
+ # configs will have `cache_options[:path]`. `cache_options` is marked
43
+ # deprecated in chef/config.rb but doesn't currently trigger a warning.
44
+ # See also: CHEF-3715
45
+ def initialize(cache_path = nil)
46
+ @cache_path = cache_path || Dir.mktmpdir
47
+ @cache_path_created = false
48
+ end
49
+
50
+ # Adds +value+ to the set's collection.
51
+ def add(value)
52
+ ensure_cache_path_created
53
+ FileUtils.touch(File.join(cache_path, value))
54
+ end
55
+
56
+ # Returns true if the set includes +value+
57
+ def include?(value)
58
+ File.exist?(File.join(cache_path, value))
59
+ end
60
+
61
+ private
62
+
63
+ def ensure_cache_path_created
64
+ return true if @cache_path_created
65
+ FileUtils.mkdir_p(cache_path)
66
+ @cache_path_created = true
67
+ end
68
+ end
69
+
70
+ include Ridley::Logging
71
+ include Ridley::Mixin::ShellOut
72
+ include Ridley::Mixin::Checksum
73
+
74
+ attr_reader :cookbook_path
75
+
76
+ # A PersistentSet object that tracks which files have already been
77
+ # validated.
78
+ attr_reader :validated_files
79
+
80
+ # Create a new SyntaxCheck object
81
+ #
82
+ # @param [String] cookbook_path
83
+ # the (on disk) path to the cookbook
84
+ def initialize(cookbook_path)
85
+ @cookbook_path = cookbook_path
86
+ @validated_files = PersistentSet.new
87
+ end
88
+
89
+ def ruby_files
90
+ Dir[File.join(cookbook_path, '**', '*.rb')]
91
+ end
92
+
93
+ def untested_ruby_files
94
+ ruby_files.reject do |file|
95
+ if validated?(file)
96
+ true
97
+ else
98
+ false
99
+ end
100
+ end
101
+ end
102
+
103
+ def template_files
104
+ Dir[File.join(cookbook_path, '**', '*.erb')]
105
+ end
106
+
107
+ def untested_template_files
108
+ template_files.reject do |file|
109
+ if validated?(file)
110
+ true
111
+ else
112
+ false
113
+ end
114
+ end
115
+ end
116
+
117
+ def validated?(file)
118
+ validated_files.include?(checksum(file))
119
+ end
120
+
121
+ def validated(file)
122
+ validated_files.add(checksum(file))
123
+ end
124
+
125
+ def validate_ruby_files
126
+ untested_ruby_files.each do |ruby_file|
127
+ return false unless validate_ruby_file(ruby_file)
128
+ validated(ruby_file)
129
+ end
130
+ end
131
+
132
+ def validate_templates
133
+ untested_template_files.each do |template|
134
+ return false unless validate_template(template)
135
+ validated(template)
136
+ end
137
+ end
138
+
139
+ def validate_template(erb_file)
140
+ result = shell_out("erubis -x #{erb_file} | ruby -c")
141
+ result.error!
142
+ true
143
+ rescue Mixlib::ShellOut::ShellCommandFailed
144
+ file_relative_path = erb_file[/^#{Regexp.escape(cookbook_path+File::Separator)}(.*)/, 1]
145
+ log.error { "Erb template #{file_relative_path} has a syntax error:" }
146
+ result.stderr.each_line { |l| Ridley.log..fatal(l.chomp) }
147
+ false
148
+ end
149
+
150
+ def validate_ruby_file(ruby_file)
151
+ result = shell_out("ruby -c #{ruby_file}")
152
+ result.error!
153
+ true
154
+ rescue Mixlib::ShellOut::ShellCommandFailed
155
+ file_relative_path = ruby_file[/^#{Regexp.escape(cookbook_path+File::Separator)}(.*)/, 1]
156
+ log.error { "Cookbook file #{file_relative_path} has a ruby syntax error:" }
157
+ result.stderr.each_line { |l| Ridley.log.error(l.chomp) }
158
+ false
159
+ end
160
+ end
161
+ end
162
+ end
@@ -0,0 +1,55 @@
1
+ require 'digest'
2
+
3
+ module Ridley::Chef
4
+ # @author Jamie Winsor <jamie@vialstudios.com>
5
+ #
6
+ # Borrowed and modified from: {https://github.com/opscode/chef/blob/11.4.0/lib/chef/digester.rb}
7
+ class Digester
8
+ class << self
9
+ def instance
10
+ @instance ||= new
11
+ end
12
+
13
+ def checksum_for_file(*args)
14
+ instance.checksum_for_file(*args)
15
+ end
16
+
17
+ def md5_checksum_for_file(*args)
18
+ instance.generate_md5_checksum_for_file(*args)
19
+ end
20
+ end
21
+
22
+ def validate_checksum(*args)
23
+ self.class.validate_checksum(*args)
24
+ end
25
+
26
+ def checksum_for_file(file)
27
+ generate_checksum(file)
28
+ end
29
+
30
+ def generate_checksum(file)
31
+ checksum_file(file, Digest::SHA256.new)
32
+ end
33
+
34
+ def generate_md5_checksum_for_file(file)
35
+ checksum_file(file, Digest::MD5.new)
36
+ end
37
+
38
+ def generate_md5_checksum(io)
39
+ checksum_io(io, Digest::MD5.new)
40
+ end
41
+
42
+ private
43
+
44
+ def checksum_file(file, digest)
45
+ File.open(file, 'rb') { |f| checksum_io(f, digest) }
46
+ end
47
+
48
+ def checksum_io(io, digest)
49
+ while chunk = io.read(1024 * 8)
50
+ digest.update(chunk)
51
+ end
52
+ digest.hexdigest
53
+ end
54
+ end
55
+ end
data/lib/ridley/errors.rb CHANGED
@@ -33,6 +33,8 @@ module Ridley
33
33
  end
34
34
  end
35
35
 
36
+ class CookbookSyntaxError < RidleyError; end
37
+
36
38
  class BootstrapError < RidleyError; end
37
39
  class ClientKeyFileNotFound < BootstrapError; end
38
40
  class EncryptedDataBagSecretNotFound < BootstrapError; end
@@ -0,0 +1,3 @@
1
+ Dir["#{File.dirname(__FILE__)}/mixin/*.rb"].sort.each do |path|
2
+ require "ridley/mixin/#{File.basename(path, '.rb')}"
3
+ end
@@ -0,0 +1,14 @@
1
+ module Ridley::Mixin
2
+ # @author Jamie Winsor <jamie@vialstudios.com>
3
+ #
4
+ # Inspired by and dependency-free replacement for
5
+ # {https://github.com/opscode/chef/blob/11.4.0/lib/chef/mixin/checksum.rb}
6
+ module Checksum
7
+ # @param [String] file
8
+ #
9
+ # @return [String]
10
+ def checksum(file)
11
+ Ridley::Chef::Digester.checksum_for_file(file)
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,23 @@
1
+ require 'mixlib/shellout'
2
+
3
+ module Ridley::Mixin
4
+ # @author Jamie Winsor <jamie@vialstudios.com>
5
+ module ShellOut
6
+ # @return [Mixlib::ShellOut]
7
+ def shell_out(*command_args)
8
+ cmd = Mixlib::ShellOut.new(*command_args)
9
+ if STDOUT.tty?
10
+ cmd.live_stream = STDOUT
11
+ end
12
+ cmd.run_command
13
+ cmd
14
+ end
15
+
16
+ # @return [Mixlib::ShellOut]
17
+ def shell_out!(*command_args)
18
+ cmd = shell_out(*command_args)
19
+ cmd.error!
20
+ cmd
21
+ end
22
+ end
23
+ end
@@ -150,16 +150,19 @@ module Ridley
150
150
  # a JSON blob containing file names, file paths, and checksums for each
151
151
  # that describe the cookbook version being uploaded.
152
152
  #
153
- # @option options [Boolean] :freeze
154
153
  # @option options [Boolean] :force
154
+ # Upload the Cookbook even if the version already exists and is frozen on
155
+ # the target Chef Server
156
+ # @option options [Boolean] :freeze
157
+ # Freeze the uploaded Cookbook on the Chef Server so that it cannot be
158
+ # overwritten
155
159
  #
156
160
  # @return [Hash]
157
161
  def save(client, name, version, manifest, options = {})
158
- freeze = options.fetch(:freeze, false)
159
- force = options.fetch(:force, false)
162
+ options.reverse_merge(force: false, freeze: false)
160
163
 
161
164
  url = "cookbooks/#{name}/#{version}"
162
- url << "?force=true" if force
165
+ url << "?force=true" if options[:force]
163
166
 
164
167
  client.connection.put(url, manifest)
165
168
  end
@@ -168,6 +171,43 @@ module Ridley
168
171
  raise NotImplementedError
169
172
  end
170
173
 
174
+ # Uploads a cookbook to the remote Chef server from the contents of a filepath
175
+ #
176
+ # @param [Ridley::Client] client
177
+ # @param [String] path
178
+ # path to a cookbook on local disk
179
+ #
180
+ # @option options [String] :name
181
+ # automatically populated by the metadata of the cookbook at the given path, but
182
+ # in the event that the metadata does not contain a name it can be specified with
183
+ # this option
184
+ # @option options [Boolean] :force (false)
185
+ # Upload the Cookbook even if the version already exists and is frozen on
186
+ # the target Chef Server
187
+ # @option options [Boolean] :freeze (false)
188
+ # Freeze the uploaded Cookbook on the Chef Server so that it cannot be
189
+ # overwritten
190
+ # @option options [Boolean] :validate (true)
191
+ # Validate the contents of the cookbook before uploading
192
+ #
193
+ # @return [Hash]
194
+ def upload(client, path, options = {})
195
+ options = options.reverse_merge(validate: true, force: false, freeze: false)
196
+ cookbook = Ridley::Chef::Cookbook.from_path(path, options.slice(:name))
197
+
198
+ if options[:validate]
199
+ cookbook.validate
200
+ end
201
+
202
+ name = options[:name] || cookbook.name
203
+ checksums = cookbook.checksums.dup
204
+ sandbox = client.sandbox.create(checksums.keys)
205
+
206
+ sandbox.upload(checksums)
207
+ sandbox.commit
208
+ save(client, name, cookbook.version, cookbook.to_json, options.slice(:force, :freeze))
209
+ end
210
+
171
211
  # Return a list of versions for the given cookbook present on the remote Chef server
172
212
  #
173
213
  # @param [Ridley::Client] client
@@ -1,3 +1,3 @@
1
1
  module Ridley
2
- VERSION = "0.7.0.rc4"
2
+ VERSION = "0.7.0"
3
3
  end
data/ridley.gemspec CHANGED
@@ -19,8 +19,10 @@ Gem::Specification.new do |s|
19
19
 
20
20
  s.add_runtime_dependency 'json', '>= 1.5.0'
21
21
  s.add_runtime_dependency 'multi_json', '>= 1.0.4'
22
- s.add_runtime_dependency 'chozo', '>= 0.5.0'
22
+ s.add_runtime_dependency 'chozo', '>= 0.6.0'
23
23
  s.add_runtime_dependency 'mixlib-log', '>= 1.3.0'
24
+ s.add_runtime_dependency 'mixlib-shellout', '>= 1.1.0'
25
+ s.add_runtime_dependency 'mixlib-config', '>= 1.1.0'
24
26
  s.add_runtime_dependency 'mixlib-authentication', '>= 1.3.0'
25
27
  s.add_runtime_dependency 'addressable'
26
28
  s.add_runtime_dependency 'faraday', '>= 0.8.4'
@@ -0,0 +1,11 @@
1
+ Description
2
+ ===========
3
+
4
+ Requirements
5
+ ============
6
+
7
+ Attributes
8
+ ==========
9
+
10
+ Usage
11
+ =====
@@ -0,0 +1,6 @@
1
+ # Attribute:: default
2
+ #
3
+ # Copyright 2012, YOUR_COMPANY_NAME
4
+ #
5
+ # All rights reserved - Do Not Redistribute
6
+ #
@@ -0,0 +1,6 @@
1
+ # Definition: bad_def
2
+ #
3
+ # Copyright 2012, YOUR_COMPANY_NAME
4
+ #
5
+ # All rights reserved - Do Not Redistribute
6
+ #
@@ -0,0 +1,2 @@
1
+ # file.h
2
+ hello
@@ -0,0 +1,2 @@
1
+ # file.h
2
+ hello, ubuntu
@@ -0,0 +1,6 @@
1
+ # Library: my_lib
2
+ #
3
+ # Copyright 2012, YOUR_COMPANY_NAME
4
+ #
5
+ # All rights reserved - Do Not Redistribute
6
+ #
@@ -0,0 +1,7 @@
1
+ name "example_cookbook"
2
+ maintainer "Jamie Winsor"
3
+ maintainer_email "jamie@vialstudios.com"
4
+ license "Apache 2.0"
5
+ description "Installs/Configures example_cookbook"
6
+ long_description IO.read(File.join(File.dirname(__FILE__), "README.md"))
7
+ version "0.1.0"
@@ -0,0 +1,6 @@
1
+ # Provider: defprovider
2
+ #
3
+ # Copyright 2012, YOUR_COMPANY_NAME
4
+ #
5
+ # All rights reserved - Do Not Redistribute
6
+ #
@@ -0,0 +1,6 @@
1
+ # Recipe: default
2
+ #
3
+ # Copyright 2012, YOUR_COMPANY_NAME
4
+ #
5
+ # All rights reserved - Do Not Redistribute
6
+ #
@@ -0,0 +1,6 @@
1
+ # Resource: defresource
2
+ #
3
+ # Copyright 2012, YOUR_COMPANY_NAME
4
+ #
5
+ # All rights reserved - Do Not Redistribute
6
+ #