stove 5.1.0 → 5.2.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +4 -0
- data/README.md +15 -0
- data/lib/stove.rb +17 -15
- data/lib/stove/artifactory.rb +83 -0
- data/lib/stove/cli.rb +27 -4
- data/lib/stove/community.rb +1 -1
- data/lib/stove/error.rb +2 -0
- data/lib/stove/plugins/artifactory.rb +14 -0
- data/lib/stove/runner.rb +5 -1
- data/lib/stove/version.rb +1 -1
- data/spec/fixtures/integration_cookbook/attributes/default.rb +1 -0
- data/spec/fixtures/integration_cookbook/metadata.rb +2 -0
- data/spec/fixtures/integration_cookbook/recipes/default.rb +1 -0
- data/spec/integration/artifactory_spec.rb +90 -0
- data/spec/spec_helper.rb +7 -1
- data/spec/unit/artifactory_spec.rb +131 -0
- data/stove.gemspec +2 -0
- data/templates/errors/artifactory_key_validation_failed.erb +11 -0
- data/templates/errors/cookbook_already_exists.erb +5 -0
- metadata +44 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 8a7b6e003eaa819a032278c65bdb28317ba36543
|
4
|
+
data.tar.gz: 71ab7d2ff0f46152784c3e4d0d60dec1cf157f01
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: cf2361dfafbaaca8e631d0b807f6b08c9fb0c1322c0865a7ac1188fc07c1e3ada760b4bd2af1712f2e295b5595d8bd3cc0869e82a534d0f0fd71f97ea953c5c7
|
7
|
+
data.tar.gz: a8a0a61d146ac8c7df835a5ff6613528d69c664e293edbffeca7fd7b8a9c8cf34f642f7a2c2492360615ac0c75b8f42116d96d299ea485ce637156692ed83ec1
|
data/CHANGELOG.md
CHANGED
@@ -2,6 +2,10 @@
|
|
2
2
|
|
3
3
|
This is the Changelog for the Stove gem.
|
4
4
|
|
5
|
+
## v5.2.0 (2017-05-12)
|
6
|
+
|
7
|
+
- Support for uploading to Artifactory
|
8
|
+
|
5
9
|
## v5.1.0 (2017-03-31)
|
6
10
|
|
7
11
|
- Match Chef's metadata behavior with chef_version and ohai_version to allow version constraints like '>= 12.5', '< 14'
|
data/README.md
CHANGED
@@ -76,6 +76,21 @@ And then use rake to publish the cookbook:
|
|
76
76
|
$ bin/rake publish
|
77
77
|
```
|
78
78
|
|
79
|
+
## Artifactory Support
|
80
|
+
|
81
|
+
In addition to uploading to a standard [Supermarket](https://github.com/chef/supermarket) server, Stove can upload to an Artifactory repository [configured to be in Chef mode](https://www.jfrog.com/confluence/display/RTF/Chef+Cookbook+Repositories).
|
82
|
+
|
83
|
+
You specify the URL to upload to via `--artifactory`, which you can find the URL Artifactory repository configuration. Your API key can be specified either in the `$ARTIFACTORY_API_KEY` environment variable or via `--artifactory-key`:
|
84
|
+
|
85
|
+
```bash
|
86
|
+
# API key in $ARTIFACTORY_API_KEY
|
87
|
+
$ stove --artifactory https://artifactory.example.com/api/chef/nameofrepo
|
88
|
+
# API key in a file
|
89
|
+
$ stove --artifactory https://artifactory.example.com/api/chef/nameofrepo --artifactory-key=@path/to/key/file
|
90
|
+
# API key on the command line
|
91
|
+
$ stove --artifactory https://artifactory.example.com/api/chef/nameofrepo --artifactory-key=XZg6tefUZYfiglmSbQ1s1e6habk
|
92
|
+
```
|
93
|
+
|
79
94
|
## Contributing
|
80
95
|
|
81
96
|
1. Fork it
|
data/lib/stove.rb
CHANGED
@@ -2,18 +2,19 @@ require 'logify'
|
|
2
2
|
require 'pathname'
|
3
3
|
|
4
4
|
module Stove
|
5
|
-
autoload :
|
6
|
-
autoload :
|
7
|
-
autoload :
|
8
|
-
autoload :
|
9
|
-
autoload :
|
10
|
-
autoload :
|
11
|
-
autoload :
|
12
|
-
autoload :
|
13
|
-
autoload :
|
14
|
-
autoload :
|
15
|
-
autoload :
|
16
|
-
autoload :
|
5
|
+
autoload :Artifactory, 'stove/artifactory'
|
6
|
+
autoload :Community, 'stove/community'
|
7
|
+
autoload :Config, 'stove/config'
|
8
|
+
autoload :Cookbook, 'stove/cookbook'
|
9
|
+
autoload :Cli, 'stove/cli'
|
10
|
+
autoload :Error, 'stove/error'
|
11
|
+
autoload :Filter, 'stove/filter'
|
12
|
+
autoload :Mash, 'stove/mash'
|
13
|
+
autoload :Packager, 'stove/packager'
|
14
|
+
autoload :Runner, 'stove/runner'
|
15
|
+
autoload :Util, 'stove/util'
|
16
|
+
autoload :Validator, 'stove/validator'
|
17
|
+
autoload :VERSION, 'stove/version'
|
17
18
|
|
18
19
|
module Middleware
|
19
20
|
autoload :ChefAuthentication, 'stove/middlewares/chef_authentication'
|
@@ -28,9 +29,10 @@ module Stove
|
|
28
29
|
end
|
29
30
|
|
30
31
|
module Plugin
|
31
|
-
autoload :
|
32
|
-
autoload :
|
33
|
-
autoload :
|
32
|
+
autoload :Artifactory, 'stove/plugins/artifactory'
|
33
|
+
autoload :Base, 'stove/plugins/base'
|
34
|
+
autoload :Community, 'stove/plugins/community'
|
35
|
+
autoload :Git, 'stove/plugins/git'
|
34
36
|
end
|
35
37
|
|
36
38
|
#
|
@@ -0,0 +1,83 @@
|
|
1
|
+
require 'net/http'
|
2
|
+
|
3
|
+
module Stove
|
4
|
+
class Artifactory
|
5
|
+
include Mixin::Instanceable
|
6
|
+
|
7
|
+
#
|
8
|
+
# Upload a cookbook to an Artifactory server.
|
9
|
+
#
|
10
|
+
# @param [Cookbook] cookbook
|
11
|
+
# the cookbook to upload
|
12
|
+
#
|
13
|
+
def upload(cookbook, extended_metadata = false)
|
14
|
+
# Artifactory doesn't prevent uploading over an existing release in
|
15
|
+
# some cases so let's check for that. Seriously never do this, go delete
|
16
|
+
# and then re-upload if you have to.
|
17
|
+
response = request(:get, "api/v1/cookbooks/#{cookbook.name}/versions/#{cookbook.version}")
|
18
|
+
# Artifactory's version of the cookbook_version endpoint returns an
|
19
|
+
# empty 200 on an unknown version.
|
20
|
+
unless response.code == '404' || (response.code == '200' && response.body.to_s == '')
|
21
|
+
raise Error::CookbookAlreadyExists.new(cookbook: cookbook)
|
22
|
+
end
|
23
|
+
|
24
|
+
# Run the upload.
|
25
|
+
response = request(:post, "api/v1/cookbooks/#{cookbook.name}.tgz") do |req|
|
26
|
+
req.body_stream = cookbook.tarball(extended_metadata)
|
27
|
+
req.content_length = req.body_stream.size
|
28
|
+
req['Content-Type'] = 'application/x-binary'
|
29
|
+
end
|
30
|
+
response.error! unless response.code == '201'
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
#
|
36
|
+
# Create an HTTP connect to the Artifactory server.
|
37
|
+
#
|
38
|
+
# @return [Net::HTTP]
|
39
|
+
#
|
40
|
+
def connection
|
41
|
+
@connection ||= begin
|
42
|
+
uri = URI(Config.artifactory.strip)
|
43
|
+
# Open the HTTP connection to artifactory.
|
44
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
45
|
+
if uri.scheme == 'https'
|
46
|
+
http.use_ssl = true
|
47
|
+
# Mimic the behavior of the Cookbook uploader for SSL verification.
|
48
|
+
if ENV['STOVE_NO_SSL_VERIFY'] || !Config.ssl_verify
|
49
|
+
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
50
|
+
end
|
51
|
+
end
|
52
|
+
http.start
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
#
|
57
|
+
# Send an HTTP request to the Artifactory server.
|
58
|
+
#
|
59
|
+
# @param [Symbol] method
|
60
|
+
# HTTP method to use
|
61
|
+
#
|
62
|
+
# @param [String] path
|
63
|
+
# URI path to request
|
64
|
+
#
|
65
|
+
# @param [Proc] block
|
66
|
+
# Optional block to set request values
|
67
|
+
#
|
68
|
+
# @return [Net::HTTPResponse]
|
69
|
+
#
|
70
|
+
def request(method, path, &block)
|
71
|
+
uri_string = Config.artifactory.strip
|
72
|
+
# Make sure we end up with the right number of separators.
|
73
|
+
uri_string << '/' unless uri_string.end_with?('/')
|
74
|
+
uri_string << path
|
75
|
+
uri = URI(uri_string)
|
76
|
+
request = Net::HTTP.const_get(method.to_s.capitalize).new(uri)
|
77
|
+
request['X-Jfrog-Art-Api'] = Config.artifactory_key.strip
|
78
|
+
block.call(request) if block
|
79
|
+
connection.request(request)
|
80
|
+
end
|
81
|
+
|
82
|
+
end
|
83
|
+
end
|
data/lib/stove/cli.rb
CHANGED
@@ -36,10 +36,12 @@ module Stove
|
|
36
36
|
end
|
37
37
|
|
38
38
|
# Override configs
|
39
|
-
Config.endpoint
|
40
|
-
Config.username
|
41
|
-
Config.key
|
42
|
-
Config.
|
39
|
+
Config.endpoint = options[:endpoint] if options[:endpoint]
|
40
|
+
Config.username = options[:username] if options[:username]
|
41
|
+
Config.key = options[:key] if options[:key]
|
42
|
+
Config.artifactory = options[:artifactory] if options[:artifactory]
|
43
|
+
Config.artifactory_key = options[:artifactory_key] if options[:artifactory_key]
|
44
|
+
Config.ssl_verify = options[:ssl_verify]
|
43
45
|
|
44
46
|
# Set the log level
|
45
47
|
Stove.log_level = options[:log_level]
|
@@ -141,6 +143,23 @@ module Stove
|
|
141
143
|
options[:sign] = true
|
142
144
|
end
|
143
145
|
|
146
|
+
opts.separator ''
|
147
|
+
opts.separator 'Artifactory Options:'
|
148
|
+
|
149
|
+
opts.on('--artifactory [URL]', 'URL for the Artifactory repository') do |v|
|
150
|
+
options[:artifactory] = v
|
151
|
+
end
|
152
|
+
|
153
|
+
opts.on('--artifactory-key [KEY]', 'Artifactory API key to use') do |v|
|
154
|
+
options[:artifactory_key] = if v[0] == '@'
|
155
|
+
# If passed a key looking like @foo, read it as a file. This allows
|
156
|
+
# passing in the key securely.
|
157
|
+
IO.read(File.expand_path(v[1..-1]))
|
158
|
+
else
|
159
|
+
v
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
144
163
|
opts.separator ''
|
145
164
|
opts.separator 'Global Options:'
|
146
165
|
|
@@ -182,6 +201,10 @@ module Stove
|
|
182
201
|
:branch => 'master',
|
183
202
|
:sign => false,
|
184
203
|
|
204
|
+
# Artifactory options
|
205
|
+
:artifactory => false,
|
206
|
+
:artifactory_key => ENV['ARTIFACTORY_API_KEY'],
|
207
|
+
|
185
208
|
# Global options
|
186
209
|
:log_level => :warn,
|
187
210
|
:path => Dir.pwd,
|
data/lib/stove/community.rb
CHANGED
data/lib/stove/error.rb
CHANGED
@@ -40,6 +40,7 @@ module Stove
|
|
40
40
|
class GitFailed < StoveError; end
|
41
41
|
class MetadataNotFound < StoveError; end
|
42
42
|
class ServerUnavailable < StoveError; end
|
43
|
+
class CookbookAlreadyExists < StoveError; end
|
43
44
|
|
44
45
|
# Validations
|
45
46
|
class ValidationFailed < StoveError; end
|
@@ -49,5 +50,6 @@ module Stove
|
|
49
50
|
class GitCleanValidationFailed < ValidationFailed; end
|
50
51
|
class GitRepositoryValidationFailed < ValidationFailed; end
|
51
52
|
class GitUpToDateValidationFailed < ValidationFailed; end
|
53
|
+
class ArtifactoryKeyValidationFailed < ValidationFailed; end
|
52
54
|
end
|
53
55
|
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
module Stove
|
2
|
+
class Plugin::Artifactory < Plugin::Base
|
3
|
+
id 'artifactory'
|
4
|
+
description 'Publish the release to an Artifactory server'
|
5
|
+
|
6
|
+
validate(:key) do
|
7
|
+
Config.artifactory_key && !Config.artifactory_key.strip.empty?
|
8
|
+
end
|
9
|
+
|
10
|
+
run('Publishing the release to the Artifactory server') do
|
11
|
+
Artifactory.upload(cookbook, options[:extended_metadata])
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
data/lib/stove/runner.rb
CHANGED
data/lib/stove/version.rb
CHANGED
@@ -0,0 +1 @@
|
|
1
|
+
default['key'] = 'value'
|
@@ -0,0 +1 @@
|
|
1
|
+
log 'this is a test recipe'
|
@@ -0,0 +1,90 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
# This integration test uses some environment variables to configure which
|
4
|
+
# Artifactory server to talk to, as there is no ArtifactoryZero to test against.
|
5
|
+
# If those aren't present, we skip the tests.
|
6
|
+
#
|
7
|
+
# $TEST_STOVE_ARTIFACTORY - URL to the Chef virtual repository.
|
8
|
+
# $TEST_STOVE_ARTIFACTORY_REAL - URL to the non-virtual repository.
|
9
|
+
# $TEST_STOVE_ARTIFACTORY_API_KEY - API key to use.
|
10
|
+
|
11
|
+
describe 'artifactory integration test', artifactory_integration: true do
|
12
|
+
include RSpecCommand
|
13
|
+
let(:upload_url) { ENV['TEST_STOVE_ARTIFACTORY'] }
|
14
|
+
let(:delete_url) { ENV['TEST_STOVE_ARTIFACTORY_REAL'] }
|
15
|
+
let(:api_key) { ENV['TEST_STOVE_ARTIFACTORY_API_KEY'] }
|
16
|
+
around do |ex|
|
17
|
+
WebMock.disable!
|
18
|
+
request(:delete, "#{delete_url}/stove_integration_test")
|
19
|
+
begin
|
20
|
+
ex.run
|
21
|
+
ensure
|
22
|
+
request(:delete, "#{delete_url}/stove_integration_test")
|
23
|
+
WebMock.enable!
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def request(method, url)
|
28
|
+
uri = URI(url)
|
29
|
+
req = Net::HTTP.const_get(method.to_s.capitalize).new(uri)
|
30
|
+
req['X-Jfrog-Art-Api'] = api_key
|
31
|
+
Net::HTTP.start(uri.host, uri.port, use_ssl: uri.scheme == 'https') do |http|
|
32
|
+
http.request(req)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
let(:upload) { JSON.parse(request(:get, "#{upload_url}/api/v1/cookbooks/stove_integration_test/versions/1.0.0").body) }
|
37
|
+
|
38
|
+
describe 'help output' do
|
39
|
+
command 'stove --help'
|
40
|
+
its(:stdout) { is_expected.to include('--artifactory ').and(include('--artifactory-key ')) }
|
41
|
+
end
|
42
|
+
|
43
|
+
describe 'uploading a cookbook' do
|
44
|
+
context 'with no key' do
|
45
|
+
fixture_file 'integration_cookbook'
|
46
|
+
command(nil, allow_error: true) { "stove --no-git --artifactory #{upload_url}" }
|
47
|
+
|
48
|
+
it 'fails to upload' do
|
49
|
+
expect(subject.exitstatus).to_not eq 0
|
50
|
+
expect(subject.stdout).to match /You did not specify and Artifactory API key/
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
context 'with $ARTIFACTORY_API_KEY' do
|
55
|
+
before { _environment['ARTIFACTORY_API_KEY'] = api_key }
|
56
|
+
fixture_file 'integration_cookbook'
|
57
|
+
command { "stove --no-git --artifactory #{upload_url}" }
|
58
|
+
|
59
|
+
it 'uploads the cookbook' do
|
60
|
+
expect(subject.stdout).to eq ''
|
61
|
+
expect(upload['version']).to eq '1.0.0'
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
context 'with --artifactory-key=@key' do
|
66
|
+
fixture_file 'integration_cookbook'
|
67
|
+
file('key') { api_key }
|
68
|
+
command { "stove --no-git --artifactory #{upload_url} --artifactory-key=@key" }
|
69
|
+
|
70
|
+
it 'uploads the cookbook' do
|
71
|
+
expect(subject.stdout).to eq ''
|
72
|
+
expect(upload['version']).to eq '1.0.0'
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
context 'with --artifactory-key=key' do
|
77
|
+
fixture_file 'integration_cookbook'
|
78
|
+
file('key') { api_key }
|
79
|
+
# Using allow_error here so the command isn't shown if things fail.
|
80
|
+
command(nil, allow_error: true) { "stove --no-git --artifactory #{upload_url} --artifactory-key=#{api_key}" }
|
81
|
+
|
82
|
+
it 'uploads the cookbook' do
|
83
|
+
expect(subject.stdout).to eq ''
|
84
|
+
expect(subject.exitstatus).to eq 0
|
85
|
+
expect(upload['version']).to eq '1.0.0'
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
end
|
90
|
+
end
|
data/spec/spec_helper.rb
CHANGED
@@ -1,4 +1,6 @@
|
|
1
1
|
require 'rspec'
|
2
|
+
require 'webmock/rspec'
|
3
|
+
require 'rspec_command'
|
2
4
|
require 'stove'
|
3
5
|
|
4
6
|
RSpec.configure do |config|
|
@@ -8,13 +10,17 @@ RSpec.configure do |config|
|
|
8
10
|
|
9
11
|
# Basic configuraiton
|
10
12
|
config.run_all_when_everything_filtered = true
|
11
|
-
config.filter_run(:focus)
|
13
|
+
config.filter_run(:focus) unless ENV['CI']
|
12
14
|
|
13
15
|
# Run specs in random order to surface order dependencies. If you find an
|
14
16
|
# order dependency and want to debug it, you can fix the order by providing
|
15
17
|
# the seed, which is printed after each run.
|
16
18
|
# --seed 1234
|
17
19
|
config.order = 'random'
|
20
|
+
|
21
|
+
# Don't try running the Artifactory integration tests without the config for it.
|
22
|
+
# See integration/artifactory_spec for more info.
|
23
|
+
config.filter_run_excluding(:artifactory_integration) unless ENV['TEST_STOVE_ARTIFACTORY']
|
18
24
|
end
|
19
25
|
|
20
26
|
def tmp_path
|
@@ -0,0 +1,131 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
# Can't use a double() for this because of the late-binding way StoveError
|
4
|
+
# renders the Erb content, which is after the example ends.
|
5
|
+
FakeCookbook = Struct.new(:name, :version)
|
6
|
+
|
7
|
+
describe Stove::Artifactory do
|
8
|
+
before { Stove::Config.ssl_verify = true }
|
9
|
+
|
10
|
+
describe '.upload' do
|
11
|
+
let(:cookbook) { FakeCookbook.new('testcook', '1.2.3') }
|
12
|
+
subject { described_class.upload(cookbook) }
|
13
|
+
before do
|
14
|
+
# Default configuration for the test.
|
15
|
+
Stove::Config.artifactory = 'https://artifactory.example/api/chef/chef'
|
16
|
+
Stove::Config.artifactory_key = 'secret'
|
17
|
+
# cookbook.tarball() stub.
|
18
|
+
allow(cookbook).to receive(:tarball) {|ext| StringIO.new(ext ? 'extended' : 'simple') }
|
19
|
+
end
|
20
|
+
|
21
|
+
context 'with defaults' do
|
22
|
+
it 'uploads the file' do
|
23
|
+
stub_request(:get, 'https://artifactory.example/api/chef/chef/api/v1/cookbooks/testcook/versions/1.2.3').with(headers: {'X-Jfrog-Art-Api' => 'secret'}).to_return(body: '')
|
24
|
+
stub_request(:post, 'https://artifactory.example/api/chef/chef/api/v1/cookbooks/testcook.tgz').with(headers: {'X-Jfrog-Art-Api' => 'secret'}, body: 'simple').to_return(status: 201)
|
25
|
+
expect { subject }.to_not raise_error
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
context 'with a 404 for an unknown cookbook' do
|
30
|
+
# This is how real Supermarket returns a non-existent cookbook so make sure it works with that too.
|
31
|
+
it 'uploads the file' do
|
32
|
+
stub_request(:get, 'https://artifactory.example/api/chef/chef/api/v1/cookbooks/testcook/versions/1.2.3').with(headers: {'X-Jfrog-Art-Api' => 'secret'}).to_return(status: 404, body: '{some json error}')
|
33
|
+
stub_request(:post, 'https://artifactory.example/api/chef/chef/api/v1/cookbooks/testcook.tgz').with(headers: {'X-Jfrog-Art-Api' => 'secret'}, body: 'simple').to_return(status: 201)
|
34
|
+
expect { subject }.to_not raise_error
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
context 'with extended_metadata = true' do
|
39
|
+
subject { described_class.upload(cookbook, true) }
|
40
|
+
it 'uploads with extended metadata' do
|
41
|
+
stub_request(:get, 'https://artifactory.example/api/chef/chef/api/v1/cookbooks/testcook/versions/1.2.3').with(headers: {'X-Jfrog-Art-Api' => 'secret'}).to_return(body: '')
|
42
|
+
stub_request(:post, 'https://artifactory.example/api/chef/chef/api/v1/cookbooks/testcook.tgz').with(headers: {'X-Jfrog-Art-Api' => 'secret'}, body: 'extended').to_return(status: 201)
|
43
|
+
expect { subject }.to_not raise_error
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
context 'with a colliding upload' do
|
48
|
+
it 'should raise an exception' do
|
49
|
+
stub_request(:get, 'https://artifactory.example/api/chef/chef/api/v1/cookbooks/testcook/versions/1.2.3').with(headers: {'X-Jfrog-Art-Api' => 'secret'}).to_return(body: '{some json}')
|
50
|
+
expect { subject }.to raise_error Stove::Error::CookbookAlreadyExists
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
context 'with a failed upload' do
|
55
|
+
it 'uploads the file' do
|
56
|
+
stub_request(:get, 'https://artifactory.example/api/chef/chef/api/v1/cookbooks/testcook/versions/1.2.3').with(headers: {'X-Jfrog-Art-Api' => 'secret'}).to_return(body: '')
|
57
|
+
stub_request(:post, 'https://artifactory.example/api/chef/chef/api/v1/cookbooks/testcook.tgz').with(headers: {'X-Jfrog-Art-Api' => 'secret'}, body: 'simple').to_return(status: 500)
|
58
|
+
expect { subject }.to raise_error Net::HTTPFatalError
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
context 'with a newline in the API key' do
|
63
|
+
before { Stove::Config.artifactory_key = "secret\n" }
|
64
|
+
it 'uploads the file' do
|
65
|
+
stub_request(:get, 'https://artifactory.example/api/chef/chef/api/v1/cookbooks/testcook/versions/1.2.3').with(headers: {'X-Jfrog-Art-Api' => 'secret'}).to_return(body: '')
|
66
|
+
stub_request(:post, 'https://artifactory.example/api/chef/chef/api/v1/cookbooks/testcook.tgz').with(headers: {'X-Jfrog-Art-Api' => 'secret'}, body: 'simple').to_return(status: 201)
|
67
|
+
expect { subject }.to_not raise_error
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
end
|
72
|
+
|
73
|
+
# Break encapsulation a bit to test the ssl_verify configuration.
|
74
|
+
describe '#connection' do
|
75
|
+
let(:url) { 'https://artifactory.example/api/chef/chef' }
|
76
|
+
let(:http) { double('Net::HTTP') }
|
77
|
+
# Make sure we don't use the singleton instance so this stub HTTP object
|
78
|
+
# doesn't get cached on it.
|
79
|
+
subject { described_class.send(:new).send(:connection) }
|
80
|
+
before do
|
81
|
+
allow(http).to receive(:start).and_return(http)
|
82
|
+
Stove::Config.artifactory = url
|
83
|
+
end
|
84
|
+
|
85
|
+
context 'with an HTTPS URI' do
|
86
|
+
it 'enables TLS' do
|
87
|
+
expect(Net::HTTP).to receive(:new).with('artifactory.example', 443).and_return(http)
|
88
|
+
expect(http).to receive(:use_ssl=).with(true)
|
89
|
+
expect(subject).to eq http
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
context 'with an HTTP URI' do
|
94
|
+
let(:url) { 'http://artifactory.example/api/chef/chef' }
|
95
|
+
it 'does not enable TLS' do
|
96
|
+
expect(Net::HTTP).to receive(:new).with('artifactory.example', 80).and_return(http)
|
97
|
+
expect(http).to_not receive(:use_ssl=)
|
98
|
+
expect(subject).to eq http
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
context 'with Config.ssl_verify = false' do
|
103
|
+
before { Stove::Config.ssl_verify = false }
|
104
|
+
it 'sets verify mode VERIFY_NONE' do
|
105
|
+
expect(Net::HTTP).to receive(:new).with('artifactory.example', 443).and_return(http)
|
106
|
+
expect(http).to receive(:use_ssl=).with(true)
|
107
|
+
expect(http).to receive(:verify_mode=).with(OpenSSL::SSL::VERIFY_NONE)
|
108
|
+
expect(subject).to eq http
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
context 'with $STOVE_NO_SSL_VERIFY' do
|
113
|
+
around do |ex|
|
114
|
+
old_value = ENV['STOVE_NO_SSL_VERIFY']
|
115
|
+
ENV['STOVE_NO_SSL_VERIFY'] = 'true'
|
116
|
+
begin
|
117
|
+
ex.run
|
118
|
+
ensure
|
119
|
+
ENV['STOVE_NO_SSL_VERIFY'] = old_value
|
120
|
+
end
|
121
|
+
end
|
122
|
+
it 'sets verify mode VERIFY_NONE' do
|
123
|
+
expect(Net::HTTP).to receive(:new).with('artifactory.example', 443).and_return(http)
|
124
|
+
expect(http).to receive(:use_ssl=).with(true)
|
125
|
+
expect(http).to receive(:verify_mode=).with(OpenSSL::SSL::VERIFY_NONE)
|
126
|
+
expect(subject).to eq http
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
end
|
131
|
+
end
|
data/stove.gemspec
CHANGED
@@ -29,4 +29,6 @@ Gem::Specification.new do |spec|
|
|
29
29
|
spec.add_development_dependency 'community-zero', '~> 2.0'
|
30
30
|
spec.add_development_dependency 'rake'
|
31
31
|
spec.add_development_dependency 'rspec', '~> 3.0'
|
32
|
+
spec.add_development_dependency 'rspec-command', '~> 1.0'
|
33
|
+
spec.add_development_dependency 'webmock', '~> 3.0'
|
32
34
|
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
You did not specify an Artifactory API key! You can pass the key either via a command line argument:
|
2
|
+
|
3
|
+
stove --artifactory-key sUeEpLXJvfhw3UZHbVPrGCgdou8VI6fXpD5sUHd0pKAxCmuGWNHLgerpFPkCJ2EjBBPYcM4My
|
4
|
+
|
5
|
+
Or via a file path:
|
6
|
+
|
7
|
+
stove --artifactory-key @~/.artifactory/api.key
|
8
|
+
|
9
|
+
Or via an environment variable:
|
10
|
+
|
11
|
+
export ARTIFACTORY_API_KEY=sUeEpLXJvfhw3UZHbVPrGCgdou8VI6fXpD5sUHd0pKAxCmuGWNHLgerpFPkCJ2EjBBPYcM4My
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: stove
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 5.
|
4
|
+
version: 5.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Seth Vargo
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2017-
|
11
|
+
date: 2017-05-12 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: chef-api
|
@@ -108,6 +108,34 @@ dependencies:
|
|
108
108
|
- - "~>"
|
109
109
|
- !ruby/object:Gem::Version
|
110
110
|
version: '3.0'
|
111
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
name: rspec-command
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - "~>"
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '1.0'
|
118
|
+
type: :development
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - "~>"
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: '1.0'
|
125
|
+
- !ruby/object:Gem::Dependency
|
126
|
+
name: webmock
|
127
|
+
requirement: !ruby/object:Gem::Requirement
|
128
|
+
requirements:
|
129
|
+
- - "~>"
|
130
|
+
- !ruby/object:Gem::Version
|
131
|
+
version: '3.0'
|
132
|
+
type: :development
|
133
|
+
prerelease: false
|
134
|
+
version_requirements: !ruby/object:Gem::Requirement
|
135
|
+
requirements:
|
136
|
+
- - "~>"
|
137
|
+
- !ruby/object:Gem::Version
|
138
|
+
version: '3.0'
|
111
139
|
description: A utility for releasing Chef community cookbooks
|
112
140
|
email:
|
113
141
|
- sethvargo@gmail.com
|
@@ -137,6 +165,7 @@ files:
|
|
137
165
|
- features/support/stove.pem
|
138
166
|
- features/support/stove/git.rb
|
139
167
|
- lib/stove.rb
|
168
|
+
- lib/stove/artifactory.rb
|
140
169
|
- lib/stove/cli.rb
|
141
170
|
- lib/stove/community.rb
|
142
171
|
- lib/stove/config.rb
|
@@ -150,6 +179,7 @@ files:
|
|
150
179
|
- lib/stove/mixins/optionable.rb
|
151
180
|
- lib/stove/mixins/validatable.rb
|
152
181
|
- lib/stove/packager.rb
|
182
|
+
- lib/stove/plugins/artifactory.rb
|
153
183
|
- lib/stove/plugins/base.rb
|
154
184
|
- lib/stove/plugins/community.rb
|
155
185
|
- lib/stove/plugins/git.rb
|
@@ -158,15 +188,22 @@ files:
|
|
158
188
|
- lib/stove/util.rb
|
159
189
|
- lib/stove/validator.rb
|
160
190
|
- lib/stove/version.rb
|
191
|
+
- spec/fixtures/integration_cookbook/attributes/default.rb
|
192
|
+
- spec/fixtures/integration_cookbook/metadata.rb
|
193
|
+
- spec/fixtures/integration_cookbook/recipes/default.rb
|
194
|
+
- spec/integration/artifactory_spec.rb
|
161
195
|
- spec/integration/cookbook_spec.rb
|
162
196
|
- spec/spec_helper.rb
|
163
197
|
- spec/support/generators.rb
|
198
|
+
- spec/unit/artifactory_spec.rb
|
164
199
|
- spec/unit/cookbook/metadata_spec.rb
|
165
200
|
- spec/unit/error_spec.rb
|
166
201
|
- stove.gemspec
|
167
202
|
- templates/errors/abstract_method.erb
|
203
|
+
- templates/errors/artifactory_key_validation_failed.erb
|
168
204
|
- templates/errors/community_key_validation_failed.erb
|
169
205
|
- templates/errors/community_username_validation_failed.erb
|
206
|
+
- templates/errors/cookbook_already_exists.erb
|
170
207
|
- templates/errors/git_clean_validation_failed.erb
|
171
208
|
- templates/errors/git_failed.erb
|
172
209
|
- templates/errors/git_repository_validation_failed.erb
|
@@ -209,8 +246,13 @@ test_files:
|
|
209
246
|
- features/support/env.rb
|
210
247
|
- features/support/stove.pem
|
211
248
|
- features/support/stove/git.rb
|
249
|
+
- spec/fixtures/integration_cookbook/attributes/default.rb
|
250
|
+
- spec/fixtures/integration_cookbook/metadata.rb
|
251
|
+
- spec/fixtures/integration_cookbook/recipes/default.rb
|
252
|
+
- spec/integration/artifactory_spec.rb
|
212
253
|
- spec/integration/cookbook_spec.rb
|
213
254
|
- spec/spec_helper.rb
|
214
255
|
- spec/support/generators.rb
|
256
|
+
- spec/unit/artifactory_spec.rb
|
215
257
|
- spec/unit/cookbook/metadata_spec.rb
|
216
258
|
- spec/unit/error_spec.rb
|