emeril 0.5.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,80 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ require 'emeril/category'
4
+ require 'emeril/git_tagger'
5
+ require 'emeril/metadata_chopper'
6
+ require 'emeril/publisher'
7
+
8
+ module Emeril
9
+
10
+ # Tags a git commit with a version string and pushes the cookbook to the
11
+ # Community Site.
12
+ #
13
+ # @author Fletcher Nichol <fnichol@nichol.ca>
14
+ #
15
+ class Releaser
16
+
17
+ # Creates a new instance.
18
+ #
19
+ # @param [Hash] options configuration for a releaser
20
+ # @option options [Logger] an optional logger instance
21
+ # @option options [String] source_path the path to a git repository
22
+ # @option options [Hash] metadata a hash of cookbook metadata
23
+ # @option options [String] category a Community Site category for the
24
+ # cookbook
25
+ # @option options [GitTagger] git_tagger a git tagger
26
+ # @option options [Publisher] publisher a publisher
27
+ # @raise [ArgumentError] if any required options are not set
28
+ #
29
+ def initialize(options = {})
30
+ @logger = options[:logger]
31
+ @tag_prefix = options[:tag_prefix]
32
+ @source_path = options.fetch(:source_path, Dir.pwd)
33
+ @metadata = options.fetch(:metadata) { default_metadata }
34
+ @category = options.fetch(:category) { default_category }
35
+ @git_tagger = options.fetch(:git_tagger) { default_git_tagger }
36
+ @publisher = options.fetch(:publisher) { default_publisher }
37
+ end
38
+
39
+ # Tags and releases a cookbook.
40
+ #
41
+ def run
42
+ git_tagger.run
43
+ publisher.run
44
+ end
45
+
46
+ private
47
+
48
+ DEFAULT_CATEGORY = "Other".freeze
49
+
50
+ attr_reader :logger, :tag_prefix, :source_path, :metadata,
51
+ :category, :git_tagger, :publisher
52
+
53
+ def default_metadata
54
+ metadata_file = File.expand_path(File.join(source_path, "metadata.rb"))
55
+ MetadataChopper.new(metadata_file)
56
+ end
57
+
58
+ def default_git_tagger
59
+ GitTagger.new(
60
+ :logger => logger,
61
+ :source_path => source_path,
62
+ :version => metadata[:version],
63
+ :tag_prefix => tag_prefix
64
+ )
65
+ end
66
+
67
+ def default_publisher
68
+ Publisher.new(
69
+ :logger => logger,
70
+ :source_path => source_path,
71
+ :name => metadata[:name],
72
+ :category => category
73
+ )
74
+ end
75
+
76
+ def default_category
77
+ Category.for_cookbook(metadata[:name]) || "Other"
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,5 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ require 'emeril/thor_tasks'
4
+
5
+ Emeril::ThorTasks.new
@@ -0,0 +1,45 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ require 'thor'
4
+ require 'chef/knife'
5
+
6
+ require 'emeril'
7
+
8
+ module Emeril
9
+
10
+ # Emeril Rake task generator.
11
+ #
12
+ # @author Fletcher Nichol <fnichol@nichol.ca>
13
+ #
14
+ class ThorTasks < Thor
15
+
16
+ namespace :emeril
17
+
18
+ attr_accessor :config
19
+
20
+ # Creates Emeril Thor tasks and allows the callee to configure it.
21
+ #
22
+ # @yield [self] gives itself to the block
23
+ #
24
+ def initialize(*args)
25
+ super
26
+ @config = { :logger => Chef::Log }
27
+ yield self if block_given?
28
+ define
29
+ end
30
+
31
+ private
32
+
33
+ def define
34
+ metadata = Emeril::MetadataChopper.new("metadata.rb")
35
+ artifact = "#{metadata[:name]}-#{metadata[:version]}"
36
+
37
+ self.class.desc "release",
38
+ "Create git tag for #{artifact} and push to the Community Site"
39
+ self.class.send(:define_method, :release) do
40
+ Chef::Knife.new.configure_chef
41
+ Emeril::Releaser.new(@config).run
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,6 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ module Emeril
4
+
5
+ VERSION = "0.5.0"
6
+ end
@@ -0,0 +1,28 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ require_relative '../spec_helper'
4
+ require 'vcr'
5
+
6
+ require 'emeril/category'
7
+
8
+ VCR.configure do |config|
9
+ config.cassette_library_dir = 'spec/fixtures/vcr_cassettes'
10
+ config.hook_into :webmock
11
+ end
12
+
13
+ describe Emeril::Category do
14
+
15
+ describe ".for_cookbook" do
16
+ it "returns a category string for a known cookbook" do
17
+ VCR.use_cassette('known_cookbook') do
18
+ Emeril::Category.for_cookbook('mysql').must_equal 'Databases'
19
+ end
20
+ end
21
+
22
+ it "returns nil for a nonexistant cookbook" do
23
+ VCR.use_cassette('nonexistant_cookbook') do
24
+ Emeril::Category.for_cookbook('fooboobunnyyaya').must_be_nil
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,147 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ require_relative '../spec_helper'
4
+ require 'tmpdir'
5
+ require 'logger'
6
+
7
+ require 'emeril/git_tagger'
8
+
9
+ describe Emeril::GitTagger do
10
+
11
+ let(:sandbox_path) { File.join(Dir.mktmpdir, "emeril") }
12
+
13
+ let(:git_tagger) do
14
+ Emeril::GitTagger.new(
15
+ :source_path => sandbox_path,
16
+ :version => "4.1.1",
17
+ :logger => nil
18
+ )
19
+ end
20
+
21
+ describe ".initialize" do
22
+
23
+ it "raises an ArgumentError when :version is missing" do
24
+ proc { Emeril::GitTagger.new }.must_raise ArgumentError
25
+ end
26
+ end
27
+
28
+ describe "#run" do
29
+
30
+ before do
31
+ make_cookbook!
32
+ end
33
+
34
+ after do
35
+ FileUtils.remove_dir(sandbox_path)
36
+ end
37
+
38
+ it "tags the repo" do
39
+ git_tagger.run
40
+ run_cmd(%{git tag}).must_match /^v4.1.1$/
41
+ end
42
+
43
+ it "disables the tag prefix" do
44
+ Emeril::GitTagger.new(
45
+ :source_path => sandbox_path,
46
+ :version => "4.1.1",
47
+ :logger => nil,
48
+ :tag_prefix => false
49
+ ).run
50
+
51
+ run_cmd(%{git tag}).must_match /^4.1.1$/
52
+ end
53
+
54
+ it "uses a custom tag prefix" do
55
+ Emeril::GitTagger.new(
56
+ :source_path => sandbox_path,
57
+ :version => "4.1.1",
58
+ :logger => nil,
59
+ :tag_prefix => "version-"
60
+ ).run
61
+
62
+ run_cmd(%{git tag}).must_match /^version-4.1.1$/
63
+ end
64
+
65
+ it "pushes the tag to the remote" do
66
+ git_tagger.run
67
+
68
+ run_cmd(%{git ls-remote --tags origin}).
69
+ must_match %r{refs/tags/v4\.1\.1$}
70
+ end
71
+
72
+ describe "when git repo is not clean" do
73
+
74
+ before do
75
+ File.open("#{sandbox_path}/README.md", "wb") { |f| f.write "Yep." }
76
+ end
77
+
78
+ it "raises GitNotCleanError" do
79
+ proc { git_tagger.run }.must_raise Emeril::GitNotCleanError
80
+ end
81
+ end
82
+
83
+ describe "when git tag exists" do
84
+
85
+ before do
86
+ run_cmd %{git tag v4.1.1}
87
+ end
88
+
89
+ it "skips tagging" do
90
+ git_tagger.expects(:tag_version).never
91
+
92
+ git_tagger.run
93
+ end
94
+ end
95
+
96
+ describe "when no git remote exists" do
97
+
98
+ before do
99
+ run_cmd %{git remote rm origin}
100
+ end
101
+
102
+ it "raises GitPushError" do
103
+ proc { git_tagger.run }.must_raise Emeril::GitPushError
104
+ end
105
+ end
106
+ end
107
+
108
+ private
109
+
110
+ def make_cookbook!
111
+ FileUtils.mkdir_p("#{sandbox_path}/recipes")
112
+ remote_dir = File.join(File.dirname(sandbox_path), "remote")
113
+
114
+ File.open("#{sandbox_path}/metadata.rb", "wb") do |f|
115
+ f.write <<-METADATA_RB.gsub(/^ {8}/, '')
116
+ name "#{name}"
117
+ maintainer "Michael Bluth"
118
+ maintainer_email "michael@bluth.com"
119
+ license "Apache 2.0"
120
+ description "Doing stuff!"
121
+ long_description "Doing stuff!"
122
+ version "4.1.1"
123
+ METADATA_RB
124
+ end
125
+ File.open("#{sandbox_path}/recipes/default.rb", "wb") do |f|
126
+ f.write <<-DEFAULT_RB.gsub(/^ {8}/, '')
127
+ directory "/tmp/yeah"
128
+
129
+ package "bash"
130
+ DEFAULT_RB
131
+ end
132
+
133
+ run_cmd [
134
+ %{git init},
135
+ %{git config user.email "you@example.com"},
136
+ %{git config user.name "Your Name"},
137
+ %{git add .},
138
+ %{git commit -m "Initial"},
139
+ %{git remote add origin #{remote_dir}},
140
+ %{git init --bare #{remote_dir}}
141
+ ].join(" && ")
142
+ end
143
+
144
+ def run_cmd(cmd, opts = {})
145
+ %x{cd #{opts.fetch(:in, sandbox_path)} && #{cmd}}
146
+ end
147
+ end
@@ -0,0 +1,70 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ require_relative '../spec_helper'
4
+
5
+ require 'emeril/metadata_chopper'
6
+
7
+ describe Emeril::MetadataChopper do
8
+
9
+ before do
10
+ FakeFS.activate!
11
+ FileUtils.mkdir_p("/tmp")
12
+ end
13
+
14
+ after do
15
+ FakeFS.deactivate!
16
+ FakeFS::FileSystem.clear
17
+ end
18
+
19
+ it "contains a :name attribute" do
20
+ stub_metadata!("banzai")
21
+ Emeril::MetadataChopper.new("/tmp/metadata.rb")[:name].must_equal "banzai"
22
+ end
23
+
24
+ it "contains a :version attribute" do
25
+ stub_metadata!("foobar", "1.2.3")
26
+ Emeril::MetadataChopper.new("/tmp/metadata.rb")[:version].must_equal "1.2.3"
27
+ end
28
+
29
+ it "raises a MetadataParseError if name attribute is missing" do
30
+ File.open("/tmp/metadata.rb", "wb") do |f|
31
+ f.write [
32
+ %{maintainer "Me"},
33
+ %{maintainer_email "me@example.com"},
34
+ %{version "1.2.3"}
35
+ ].join("\n")
36
+ end
37
+
38
+ proc { Emeril::MetadataChopper.new("/tmp/metadata.rb") }.
39
+ must_raise Emeril::MetadataParseError
40
+ end
41
+
42
+ it "raises a MetadataParseError if version attribute is missing" do
43
+ File.open("/tmp/metadata.rb", "wb") do |f|
44
+ f.write [
45
+ %{maintainer "Me"},
46
+ %{maintainer_email "me@example.com"},
47
+ %{name "pants"}
48
+ ].join("\n")
49
+ end
50
+
51
+ proc { Emeril::MetadataChopper.new("/tmp/metadata.rb") }.
52
+ must_raise Emeril::MetadataParseError
53
+ end
54
+
55
+ private
56
+
57
+ def stub_metadata!(name = "foobar", version = "5.2.1")
58
+ File.open("/tmp/metadata.rb", "wb") do |f|
59
+ f.write <<-METADATA_RB.gsub(/^ {8}/, '')
60
+ name "#{name}"
61
+ maintainer "Michael Bluth"
62
+ maintainer_email "michael@bluth.com"
63
+ license "Apache 2.0"
64
+ description "Doing stuff!"
65
+ long_description "Doing stuff!"
66
+ version "#{version}"
67
+ METADATA_RB
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,223 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ require_relative '../spec_helper'
4
+ require 'chef/knife'
5
+ require 'chef/config'
6
+
7
+ require 'emeril/publisher'
8
+
9
+ class DummyKnife < Emeril::Publisher::SharePlugin
10
+
11
+ def run ; end
12
+ end
13
+
14
+ describe Emeril::Publisher do
15
+
16
+ let(:cookbook_path) { File.join(Dir.mktmpdir, "emeril") }
17
+ let(:category) { "Utilities" }
18
+
19
+ let(:publisher) do
20
+ if ENV['DEBUG']
21
+ logger = Logger.new(STDOUT)
22
+ logger.level = Logger::DEBUG
23
+ else
24
+ logger = nil
25
+ end
26
+
27
+ Emeril::Publisher.new(
28
+ :source_path => cookbook_path,
29
+ :name => "emeril",
30
+ :category => category,
31
+ :logger => logger,
32
+ :knife_class => DummyKnife
33
+ )
34
+ end
35
+
36
+ before do
37
+ @saved = Hash.new
38
+ %w{node_name client_key}.map(&:to_sym).each do |attr|
39
+ @saved[attr] = Chef::Config[attr]
40
+ end
41
+
42
+ Chef::Config[:node_name] = "buster"
43
+ Chef::Config[:client_key] = "/tmp/buster.pem"
44
+ end
45
+
46
+ after do
47
+ %w{node_name client_key}.map(&:to_sym).each do |attr|
48
+ Chef::Config[attr] = @saved.delete(attr)
49
+ end
50
+ end
51
+
52
+ describe ".initialize" do
53
+
54
+ it "raises an ArgumentError when :name is missing" do
55
+ proc { Emeril::Publisher.new }.must_raise ArgumentError
56
+ end
57
+ end
58
+
59
+ describe "#run" do
60
+
61
+ before do
62
+ make_cookbook!
63
+ end
64
+
65
+ after do
66
+ FileUtils.remove_dir(cookbook_path)
67
+ end
68
+
69
+ it "constructs an instance of :knife_class" do
70
+ knife_obj = DummyKnife.new
71
+ DummyKnife.expects(:new).returns(knife_obj)
72
+
73
+ publisher.run
74
+ end
75
+
76
+ it "sets a sandboxed cookbook_path on the knife object" do
77
+ knife_obj = DummyKnife.new
78
+ DummyKnife.stubs(:new).returns(knife_obj)
79
+ sandbox_path = Dir.mktmpdir
80
+ Dir.stub :mktmpdir, sandbox_path do
81
+ publisher.run
82
+ end
83
+
84
+ knife_obj.config[:cookbook_path].must_equal sandbox_path
85
+ end
86
+
87
+ it "sets the cookbook name and category on the knife object" do
88
+ knife_obj = DummyKnife.new
89
+ DummyKnife.stubs(:new).returns(knife_obj)
90
+ publisher.run
91
+
92
+ knife_obj.name_args.must_equal ["emeril", category]
93
+ end
94
+
95
+ it "invokes run on the knife object" do
96
+ knife_obj = DummyKnife.new
97
+ DummyKnife.stubs(:new).returns(knife_obj)
98
+ knife_obj.expects(:run)
99
+
100
+ publisher.run
101
+ end
102
+ end
103
+
104
+ describe "SharePlugin" do
105
+
106
+ it "overrides #exit to raise an exception" do
107
+ knife_obj = Emeril::Publisher::SharePlugin.new
108
+ knife_obj.ui = Chef::Knife::UI.new(StringIO.new, StringIO.new,
109
+ StringIO.new, knife_obj.ui.config)
110
+
111
+ proc { knife_obj.run }.must_raise RuntimeError
112
+ end
113
+ end
114
+
115
+ describe "LoggingUI" do
116
+
117
+ let(:stdout) { StringIO.new }
118
+ let(:stderr) { StringIO.new }
119
+ let(:stdin) { StringIO.new }
120
+
121
+ let(:logger) do
122
+ stub(:msg => true, :err => true, :warn => true, :fatal => true)
123
+ end
124
+
125
+ describe "#msg" do
126
+
127
+ it "calls #logger.info if logger is set" do
128
+ logger.expects(:info).with("yo")
129
+
130
+ ui_with_logger.msg("yo")
131
+ end
132
+
133
+ it "calls super if logger is nil" do
134
+ ui_without_logger.msg("yo")
135
+
136
+ stdout.string.must_match /^yo$/
137
+ end
138
+ end
139
+
140
+ describe "#err" do
141
+
142
+ it "calls #logger.error if logger is set" do
143
+ logger.expects(:error).with("yolo")
144
+
145
+ ui_with_logger.err("yolo")
146
+ end
147
+
148
+ it "calls super if logger is nil" do
149
+ ui_without_logger.err("yolo")
150
+
151
+ stderr.string.must_match /^yolo$/
152
+ end
153
+ end
154
+
155
+ describe "#warn" do
156
+
157
+ it "calls #logger.warn if logger is set" do
158
+ logger.expects(:warn).with("caution")
159
+
160
+ ui_with_logger.warn("caution")
161
+ end
162
+
163
+ it "calls super if logger is nil" do
164
+ ui_without_logger.err("caution")
165
+
166
+ stderr.string.must_match /^caution$/
167
+ end
168
+ end
169
+
170
+ describe "#fatal" do
171
+
172
+ it "calls #logger.fatal if logger is set" do
173
+ logger.expects(:fatal).with("die")
174
+
175
+ ui_with_logger.fatal("die")
176
+ end
177
+
178
+ it "calls super if logger is nil" do
179
+ ui_without_logger.fatal("die")
180
+
181
+ stderr.string.must_match /die$/
182
+ end
183
+ end
184
+
185
+ private
186
+
187
+ def ui_with_logger
188
+ Emeril::Publisher::LoggingUI.new(stdout, stderr, stdin, stub, logger)
189
+ end
190
+
191
+ def ui_without_logger
192
+ Emeril::Publisher::LoggingUI.new(stdout, stderr, stdin, stub, nil)
193
+ end
194
+ end
195
+
196
+ private
197
+
198
+ def make_cookbook!
199
+ FileUtils.mkdir_p("#{cookbook_path}/recipes")
200
+ remote_dir = File.join(File.dirname(cookbook_path), "remote")
201
+
202
+ File.open("#{cookbook_path}/metadata.rb", "wb") do |f|
203
+ f.write <<-METADATA_RB.gsub(/^ {8}/, '')
204
+ name "#{name}"
205
+ maintainer "Michael Bluth"
206
+ maintainer_email "michael@bluth.com"
207
+ license "Apache 2.0"
208
+ description "Doing stuff!"
209
+ long_description "Doing stuff!"
210
+ version "4.1.1"
211
+ METADATA_RB
212
+ end
213
+ File.open("#{cookbook_path}/recipes/default.rb", "wb") do |f|
214
+ f.write <<-DEFAULT_RB.gsub(/^ {8}/, '')
215
+ directory "/tmp/yeah"
216
+
217
+ package "bash"
218
+ DEFAULT_RB
219
+ end
220
+ File.open("#{cookbook_path}/Berksfile", "wb") { |f| f.write "borkbork" }
221
+ File.open("#{cookbook_path}/.gitignore", "wb") { |f| f.write "Berksfile" }
222
+ end
223
+ end