wood-stove 3.2.9000

Sign up to get free protection for your applications and to get access to all the features.
Files changed (59) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +19 -0
  3. data/.travis.yml +17 -0
  4. data/CHANGELOG.md +112 -0
  5. data/Gemfile +2 -0
  6. data/LICENSE +202 -0
  7. data/README.md +121 -0
  8. data/Rakefile +24 -0
  9. data/bin/bake +6 -0
  10. data/bin/stove +4 -0
  11. data/features/plugins/community.feature +41 -0
  12. data/features/plugins/git.feature +40 -0
  13. data/features/step_definitions/community_steps.rb +23 -0
  14. data/features/step_definitions/config_steps.rb +7 -0
  15. data/features/step_definitions/cookbook_steps.rb +47 -0
  16. data/features/step_definitions/cucumber_steps.rb +12 -0
  17. data/features/step_definitions/git_steps.rb +67 -0
  18. data/features/support/env.rb +34 -0
  19. data/features/support/stove.pem +27 -0
  20. data/features/support/stove/git.rb +68 -0
  21. data/lib/stove.rb +85 -0
  22. data/lib/stove/cli.rb +191 -0
  23. data/lib/stove/community.rb +96 -0
  24. data/lib/stove/config.rb +76 -0
  25. data/lib/stove/cookbook.rb +120 -0
  26. data/lib/stove/cookbook/metadata.rb +206 -0
  27. data/lib/stove/error.rb +53 -0
  28. data/lib/stove/filter.rb +60 -0
  29. data/lib/stove/mash.rb +25 -0
  30. data/lib/stove/mixins/insideable.rb +13 -0
  31. data/lib/stove/mixins/instanceable.rb +24 -0
  32. data/lib/stove/mixins/optionable.rb +41 -0
  33. data/lib/stove/mixins/validatable.rb +11 -0
  34. data/lib/stove/packager.rb +153 -0
  35. data/lib/stove/plugins/base.rb +48 -0
  36. data/lib/stove/plugins/community.rb +18 -0
  37. data/lib/stove/plugins/git.rb +68 -0
  38. data/lib/stove/rake_task.rb +22 -0
  39. data/lib/stove/runner.rb +35 -0
  40. data/lib/stove/util.rb +56 -0
  41. data/lib/stove/validator.rb +68 -0
  42. data/lib/stove/version.rb +3 -0
  43. data/spec/integration/cookbook_spec.rb +42 -0
  44. data/spec/spec_helper.rb +22 -0
  45. data/spec/support/generators.rb +128 -0
  46. data/spec/unit/cookbook/metadata_spec.rb +23 -0
  47. data/spec/unit/error_spec.rb +133 -0
  48. data/templates/errors/abstract_method.erb +5 -0
  49. data/templates/errors/community_key_validation_failed.erb +3 -0
  50. data/templates/errors/community_username_validation_failed.erb +3 -0
  51. data/templates/errors/git_clean_validation_failed.erb +1 -0
  52. data/templates/errors/git_failed.erb +5 -0
  53. data/templates/errors/git_repository_validation_failed.erb +3 -0
  54. data/templates/errors/git_up_to_date_validation_failed.erb +7 -0
  55. data/templates/errors/metadata_not_found.erb +1 -0
  56. data/templates/errors/server_unavailable.erb +1 -0
  57. data/templates/errors/stove_error.erb +1 -0
  58. data/wood-stove.gemspec +32 -0
  59. metadata +216 -0
@@ -0,0 +1,24 @@
1
+ require 'bundler/gem_tasks'
2
+
3
+ require 'rspec/core/rake_task'
4
+ RSpec::Core::RakeTask.new(:unit) do |t|
5
+ t.rspec_opts = [].tap do |a|
6
+ a.push('--color')
7
+ a.push('--format progress')
8
+ end.join(' ')
9
+ end
10
+
11
+ require 'cucumber/rake/task'
12
+ Cucumber::Rake::Task.new(:acceptance) do |t|
13
+ t.cucumber_opts = [].tap do |a|
14
+ a.push('--color')
15
+ a.push('--format progress')
16
+ a.push('--strict')
17
+ a.push('--tags ~@wip')
18
+ end.join(' ')
19
+ end
20
+
21
+ desc 'Run all tests'
22
+ task :test => [:unit, :acceptance]
23
+
24
+ task :default => [:test]
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ $stderr.puts "The `bake' command is deprecated. Please use `stove' instead!"
4
+
5
+ require 'stove'
6
+ Stove::Cli.new(ARGV.dup).execute!
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'stove'
4
+ Stove::Cli.new(ARGV.dup).execute!
@@ -0,0 +1,41 @@
1
+ Feature: Community
2
+ Background:
3
+ * I have a cookbook named "bacon"
4
+
5
+ Scenario: When the username does not exist
6
+ * the Stove config at "username" is unset
7
+ * I run `stove --no-git`
8
+ * it should fail with "requires a username"
9
+
10
+ Scenario: When the key does not exist
11
+ * the Stove config at "key" is unset
12
+ * I run `stove --no-git`
13
+ * it should fail with "requires a private key"
14
+
15
+ Scenario: With the default parameters
16
+ * the community server has the cookbook:
17
+ | bacon | 1.2.3 |
18
+ * I successfully run `stove --no-git`
19
+ * the community server will have the cookbooks:
20
+ | bacon | 0.0.0 |
21
+
22
+ Scenario: Yanking a cookbook
23
+ * the community server has the cookbooks:
24
+ | bacon | 1.2.3 |
25
+ * I successfully run `stove yank -l debug`
26
+ * the community server will not have the cookbooks:
27
+ | bacon | 1.2.3 |
28
+ * the output should contain "Successfully yanked bacon!"
29
+
30
+ Scenario: Yanking a cookbook by name
31
+ * the community server has the cookbooks:
32
+ | eggs | 4.5.6 |
33
+ * I successfully run `stove yank eggs`
34
+ * the community server will not have the cookbooks:
35
+ | eggs | 4.5.6 |
36
+ * the output should not contain "Successfully yanked bacon!"
37
+ * the output should contain "Successfully yanked eggs!"
38
+
39
+ Scenario: Yanking a non-existent cookbook
40
+ * I run `stove yank ham`
41
+ * it should fail with "I could not find a cookbook named ham"
@@ -0,0 +1,40 @@
1
+ Feature: git Plugin
2
+ Background:
3
+ * I have a cookbook named "bacon"
4
+ * the community server has the cookbooks:
5
+ | bacon | 1.0.0 |
6
+
7
+ Scenario: When the directory is not a git repository
8
+ * I run `stove`
9
+ * it should fail with "does not appear to be a valid git repository"
10
+
11
+ Scenario: When the directory is dirty
12
+ * I have a cookbook named "bacon" with git support
13
+ * I write to "new" with:
14
+ """
15
+ This is new content
16
+ """
17
+ * I run `stove`
18
+ * it should fail with "has untracked files"
19
+
20
+ Scenario: When the local is out of date with the remote
21
+ * I have a cookbook named "bacon" with git support
22
+ * the remote repository has additional commits
23
+ * I run `stove -l debug`
24
+ * it should fail with "out of sync with the remote repository"
25
+
26
+ Scenario: When a git upload should be done
27
+ * I have a cookbook named "bacon" with git support
28
+ * I successfully run `stove`
29
+ * the git remote should have the tag "v0.0.0"
30
+
31
+ Scenario: When using signed tags
32
+ * I have a cookbook named "bacon" with git support
33
+ * a GPG key exists
34
+ * I successfully run `stove --sign`
35
+ * the git remote should have the signed tag "v0.0.0"
36
+
37
+ Scenario: With the git plugin disabled
38
+ * I have a cookbook named "bacon" with git support
39
+ * I successfully run `stove --no-git`
40
+ * the git remote should not have the tag "v0.0.0"
@@ -0,0 +1,23 @@
1
+ Given /^the community server has the cookbooks?:$/ do |table|
2
+ table.raw.each do |name, version|
3
+ version ||= '0.0.0'
4
+
5
+ CommunityZero::RSpec.store.add(CommunityZero::Cookbook.new(
6
+ name: name,
7
+ version: version,
8
+ category: 'Other',
9
+ ))
10
+ end
11
+ end
12
+
13
+ Then /^the community server will( not)? have the cookbooks?:$/ do |negate, table|
14
+ table.raw.each do |name, version|
15
+ cookbook = CommunityZero::RSpec.store.find(name, version)
16
+
17
+ if negate
18
+ expect(cookbook).to be_nil
19
+ else
20
+ expect(cookbook).to_not be_nil
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,7 @@
1
+ Given /^the Stove config at "(.+)" is "(.+)"/ do |variable, value|
2
+ Stove::Config.__set__(variable, value)
3
+ end
4
+
5
+ Given /^the Stove config at "(.+)" is unset/ do |variable|
6
+ Stove::Config.__unset__(variable)
7
+ end
@@ -0,0 +1,47 @@
1
+ Given /^I have a cookbook named "([\w\-]+)" at version "([\d\.]+)"$/ do |name, version|
2
+ create_cookbook(name, version)
3
+ end
4
+
5
+ Given /^I have a cookbook named "([\w\-]+)"$/ do |name|
6
+ create_cookbook(name, '0.0.0')
7
+ end
8
+
9
+ Given /^I have a cookbook named "([\w\-]+)" with git support$/ do |name|
10
+ create_cookbook(name, '0.0.0', git: true)
11
+ end
12
+
13
+
14
+ #
15
+ # Create a new cookbook with the given name and version.
16
+ #
17
+ # @param [String] name
18
+ # @param [String] version (default: 0.0.0.0)
19
+ # @param [Hash] options
20
+ #
21
+ def create_cookbook(name, version, options = {})
22
+ create_dir(name)
23
+ cd(name)
24
+
25
+ write_file('CHANGELOG.md', <<-EOH.gsub(/^ {4}/, ''))
26
+ #{name} Changelog
27
+ =================
28
+
29
+ v#{version} (#{Time.now.to_date})
30
+ ----------------------------
31
+ - This is an entry
32
+ - This is another entry
33
+ EOH
34
+
35
+ write_file('README.md', <<-EOH.gsub(/^ {4}/, ''))
36
+ This is the README for #{name}
37
+ EOH
38
+
39
+ write_file('metadata.rb', <<-EOH.gsub(/^ {4}/, ''))
40
+ name '#{name}'
41
+ version '#{version}'
42
+ EOH
43
+
44
+ if options[:git]
45
+ git_init(current_dir)
46
+ end
47
+ end
@@ -0,0 +1,12 @@
1
+ # These are steps that should really exist in cucumber, but they don't...
2
+ When /^the environment variable "(.+)" is "(.+)"/ do |variable, value|
3
+ set_env(variable, value)
4
+ end
5
+
6
+ When /^the environment variable "(.+)" is unset$/ do |variable|
7
+ set_env(variable, nil)
8
+ end
9
+
10
+ Then /^it should (pass|fail) with "(.+)"$/ do |pass_fail, partial|
11
+ self.__send__("assert_#{pass_fail}ing_with", partial)
12
+ end
@@ -0,0 +1,67 @@
1
+ Given /^the remote repository has additional commits/ do
2
+ cmd = [
3
+ 'cd "' + fake_git_remote + '"',
4
+ 'touch myfile.txt',
5
+ 'git add --force myfile.txt',
6
+ 'git commit --message "Add new file"',
7
+ ].join(' && ')
8
+
9
+ %x|#{cmd}|
10
+ end
11
+
12
+ Given /^a GPG key exists/ do
13
+ gpg_home = File.join(scratch_dir, '.gnupg')
14
+ set_env('GNUPGHOME', gpg_home)
15
+ Dir.mkdir(gpg_home)
16
+ File.chmod(0700, gpg_home)
17
+ batch_path = File.join(gpg_home, 'batch')
18
+ File.write(batch_path, <<-EOH)
19
+ %pubring #{File.join(gpg_home, 'keyring')}
20
+ %secring #{File.join(gpg_home, 'keyring.sec')}
21
+ Key-Type: DSA
22
+ Key-Length: 832
23
+ Subkey-Type: ELG-E
24
+ Subkey-Length: 800
25
+ Name-Real: Alan Smithee
26
+ Name-Email: asmithee@example.com
27
+ Expire-Date: 0
28
+ %commit
29
+ EOH
30
+ gpg_wrapper = File.join(gpg_home, 'gpg_wrapper')
31
+ File.write(gpg_wrapper, <<-EOH)
32
+ #!/bin/sh
33
+ gpg "--keyring=#{File.join(gpg_home, 'keyring')}" "--secret-keyring=#{File.join(gpg_home, 'keyring.sec')}" "$@"
34
+ EOH
35
+ File.chmod(0755, gpg_wrapper)
36
+
37
+ cmd = [
38
+ "cd \"#{current_dir}\"",
39
+ "git config gpg.program #{gpg_wrapper}",
40
+ 'git config user.signingkey asmithee@example.com',
41
+ "gpg --quiet --batch --gen-key #{batch_path}",
42
+ ].join(' && ')
43
+
44
+ %x|#{cmd}|
45
+ end
46
+
47
+
48
+ Then /^the git remote should( not)? have the commit "(.+)"$/ do |negate, message|
49
+ commits = git_commits(fake_git_remote)
50
+
51
+ if negate
52
+ expect(commits).to_not include(message)
53
+ else
54
+ expect(commits).to include(message)
55
+ end
56
+ end
57
+
58
+ Then /^the git remote should( not)? have the( signed)? tag "(.+)"$/ do |negate, signed, tag|
59
+ tags = git_tags(fake_git_remote)
60
+
61
+ if negate
62
+ expect(tags).to_not include(tag)
63
+ else
64
+ expect(tags).to include(tag)
65
+ expect(git_tag_signature?(fake_git_remote, tag)).to be_truthy if signed
66
+ end
67
+ end
@@ -0,0 +1,34 @@
1
+ require 'stove'
2
+
3
+ require 'aruba'
4
+ require 'aruba/cucumber'
5
+ require 'aruba/in_process'
6
+
7
+ Aruba::InProcess.main_class = Stove::Cli
8
+ Aruba.process = Aruba::InProcess
9
+
10
+ require 'community_zero/rspec'
11
+ CommunityZero::RSpec.start
12
+
13
+ require File.expand_path('../stove/git', __FILE__)
14
+
15
+ World(Aruba::Api)
16
+ World(Stove::Git)
17
+
18
+ Before do
19
+ CommunityZero::RSpec.reset!
20
+
21
+ Stove::Config.endpoint = CommunityZero::RSpec.url
22
+ Stove::Config.username = 'stove'
23
+ Stove::Config.key = File.expand_path('../stove.pem', __FILE__)
24
+ end
25
+
26
+ Before do
27
+ FileUtils.rm_rf(scratch_dir)
28
+ FileUtils.mkdir_p(scratch_dir)
29
+ end
30
+
31
+ # The scratch directory
32
+ def scratch_dir
33
+ @scratch_dir ||= File.expand_path('tmp/aruba/scratch')
34
+ end
@@ -0,0 +1,27 @@
1
+ -----BEGIN RSA PRIVATE KEY-----
2
+ MIIEpQIBAAKCAQEAzdbR5SpsMUlKrQle+6KhEuP9v+ZV0zRj2SMilk8jxlLDmuZ7
3
+ j+32n+envYMuQTg+Bm2nzDqcyePGCd2jyWj+TszwxZ3IyqEEzOl93vEzUwKf9WWf
4
+ sOdG3/O7i2eJMNOzCxfjqMmld7E74d3Tq+0P5McIC4awFrGWfd5DX1QmFlygvTVH
5
+ Ke3Bmf6gEXyQRhrFDdc1sfLtBZnCC5wEzZ3hrTP/Yg3zFbJl4eoTEEbQ2MtjBa/e
6
+ Nw/JmIBiLBbuzc9Sxjbf1QY5piVU/KWKbUT2bNFTvRtzT3sU75FS8MRGKnw4pNuh
7
+ wzLxqcwLRw2TrS+KWYLbB9CD3xLDo0Pcv3S9ewIDAQABAoIBAQCIew2FA5HlRBFt
8
+ li8Sbgya9+zCFrmFZtFlofRG8YZo82ubA4OeNUw3TmRcNeSvfdkybfa6ZYqcGpiu
9
+ 5fS5kRI9sYlbnnkKUaVbMN4yun8rZSwmjBFMPK0zxOnvTuEaLOQkUNy0h69dI3jv
10
+ FCRLdM37BTUCX3XWNj3wizTkumjena5tqIjvWC0YuTau74B2TQfzXcuwXRnwep6t
11
+ AGjtYyUvee2R2xbgZwA4Y8350wDbNrRqcwYsD1MplfAbOf2aNSjPbltjf7qmc0md
12
+ 2xtSf+rXCtb2T7xsIo6RWkZJ5BgxULRYt1tfDema1CtIPEqYAvwhDLKYylTef32W
13
+ hwOtDfeBAoGBAPyr5j+ebwlVrl2gw4kFfV+YjmcLaJRKK8uJww4n0t6geK7rwZpU
14
+ pmRXq8+K0IWhm6BJ9OfHhwA/BvFmOtfWE0Hl7C0aLE9K6Y/Y1cjUPhZ7lwKv/pP2
15
+ wEsfF3FceBO+lFcrnjdZ5m7hUbdxBliLvfK3mSsiI8CcptGaTvy/g3qbAoGBANCM
16
+ /Bajm32AeTghM3n0+UN5kB71quNnW4o6QxDjfHRAx9I8lVLaoKnAa1p/4hatAMwq
17
+ E9K6ZFj8SpI9heIuPIB0IfYCXWh521RNn6cf6A4x7YSLy39m3+9Fjfj6f1g5Njyj
18
+ h4EFCIuLhCFDSmMDS2p7P9TivEtO1p/W8p+SfwahAoGAMX3mUs3QyA5NYi+MPXmi
19
+ zifOtOZqLKm+nFa2qz4nss3R9cleGcG8+eimUbfKEnLOTf5Oh1vw2J5/2KcCnaZk
20
+ DUNhGJCI31s4FqINdhIEu1ioArHAdvEdR1mmuOC48H0jB1QW4JauaUefPwRXjt6I
21
+ bVODIAzG3gKRNns4P4+vzEMCgYEAt3S+MWAc4SsJ93Flagww1cVzHXj4qfB7Gz6j
22
+ TpD/Ivj1jqCSrv75xDordcH3bgEkKXV+WKp0qb7ODpUmWFBaEpmWYmW0K7q+UQuz
23
+ vP2ZUhtjmGytR9aEeWRTPsmFCmPRrUghZEK8QJ0rT2N1ZWI5jmL7RNdr0kd5D8Sz
24
+ S+I/8eECgYEA3TjKIBVeDSG7wbTgbFoRDWHK/guCWn8rRpxL6hca7I2afH0D9pR0
25
+ J5eNG2kbkq0SwlJXdXU9ADh5NSAUwVs6+bBj+Md/GOHCkYgWRGp0FZGOSTKjsuIm
26
+ dXwFj0w9pEyEneu6aa86Uac/hLK5A2vqtXRrhI2iUnRPXKadr4v0u6Y=
27
+ -----END RSA PRIVATE KEY-----
@@ -0,0 +1,68 @@
1
+ require 'fileutils'
2
+
3
+ module Stove
4
+ module Git
5
+ def git_init(path = Dir.pwd)
6
+ cmd = [
7
+ 'git init .',
8
+ 'git add --all',
9
+ 'git commit --message "Initial commit"',
10
+ 'git remote add origin file://' + fake_git_remote,
11
+ 'git push --quiet --force origin master',
12
+ ].join(' && ')
13
+
14
+ Dir.chdir(path) do
15
+ %x|#{cmd}|
16
+ end
17
+ end
18
+
19
+ def fake_git_remote
20
+ path = File.expand_path(File.join(remotes_path, 'remote.git'))
21
+ return path if File.exists?(path)
22
+
23
+ FileUtils.mkdir_p(path)
24
+ cmd = [
25
+ 'git init .',
26
+ 'git config receive.denyCurrentBranch ignore',
27
+ 'git config receive.denyNonFastforwards true',
28
+ 'git config core.sharedrepository 1',
29
+ ].join(' && ')
30
+
31
+ Dir.chdir(path) do
32
+ %x|#{cmd}|
33
+ end
34
+
35
+ path
36
+ end
37
+
38
+ def git_shas(path)
39
+ Dir.chdir(path) do
40
+ %x|git log --oneline|.split("\n").map { |line| line.split(/\s+/, 2).first.strip } rescue []
41
+ end
42
+ end
43
+
44
+ def git_commits(path)
45
+ Dir.chdir(path) do
46
+ %x|git log --oneline|.split("\n").map { |line| line.split(/\s+/, 2).last.strip } rescue []
47
+ end
48
+ end
49
+
50
+ def git_tags(path)
51
+ Dir.chdir(path) do
52
+ %x|git tag --list|.split("\n").map(&:strip) rescue []
53
+ end
54
+ end
55
+
56
+ def git_tag_signature?(path, tag)
57
+ Dir.chdir(path) do
58
+ %x|git show --show-signature #{tag}|.include?('BEGIN PGP SIGNATURE') rescue false
59
+ end
60
+ end
61
+
62
+ private
63
+
64
+ def remotes_path
65
+ @remotes_path ||= File.join(scratch_dir, 'remotes')
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,85 @@
1
+ require 'logify'
2
+ require 'pathname'
3
+
4
+ module Stove
5
+ autoload :Community, 'stove/community'
6
+ autoload :Config, 'stove/config'
7
+ autoload :Cookbook, 'stove/cookbook'
8
+ autoload :Cli, 'stove/cli'
9
+ autoload :Error, 'stove/error'
10
+ autoload :Filter, 'stove/filter'
11
+ autoload :Mash, 'stove/mash'
12
+ autoload :Packager, 'stove/packager'
13
+ autoload :Runner, 'stove/runner'
14
+ autoload :Util, 'stove/util'
15
+ autoload :Validator, 'stove/validator'
16
+ autoload :VERSION, 'stove/version'
17
+
18
+ module Middleware
19
+ autoload :ChefAuthentication, 'stove/middlewares/chef_authentication'
20
+ autoload :Exceptions, 'stove/middlewares/exceptions'
21
+ end
22
+
23
+ module Mixin
24
+ autoload :Insideable, 'stove/mixins/insideable'
25
+ autoload :Instanceable, 'stove/mixins/instanceable'
26
+ autoload :Optionable, 'stove/mixins/optionable'
27
+ autoload :Validatable, 'stove/mixins/validatable'
28
+ end
29
+
30
+ module Plugin
31
+ autoload :Base, 'stove/plugins/base'
32
+ autoload :Community, 'stove/plugins/community'
33
+ autoload :Git, 'stove/plugins/git'
34
+ end
35
+
36
+ #
37
+ # A constant to represent an unset value. +nil+ is too generic and doesn't
38
+ # allow users to specify a value as +nil+. Using this constant, we can
39
+ # safely create +set_or_return+-style methods.
40
+ #
41
+ # @return [Object]
42
+ #
43
+ UNSET_VALUE = Object.new
44
+
45
+ #
46
+ # The User-Agent to use for HTTP requests
47
+ #
48
+ # @return [String]
49
+ #
50
+ USER_AGENT = "Stove #{VERSION}"
51
+
52
+ class << self
53
+ #
54
+ # The source root of the ChefAPI gem. This is useful when requiring files
55
+ # that are relative to the root of the project.
56
+ #
57
+ # @return [Pathname]
58
+ #
59
+ def root
60
+ @root ||= Pathname.new(File.expand_path('../../', __FILE__))
61
+ end
62
+
63
+ #
64
+ # Set the log level.
65
+ #
66
+ # @example Set the log level to :info
67
+ # ChefAPI.log_level = :info
68
+ #
69
+ # @param [#to_sym] level
70
+ # the log level to set
71
+ #
72
+ def log_level=(level)
73
+ Logify.level = level.to_sym
74
+ end
75
+
76
+ #
77
+ # Get the current log level.
78
+ #
79
+ # @return [Symbol]
80
+ #
81
+ def log_level
82
+ Logify.level
83
+ end
84
+ end
85
+ end