ridley 0.7.0.rc4 → 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
+ #