emeril 0.5.0

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